From 58d4ba0d8f76b689448fbabbd24323ef3bdaf33c Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 9 Mar 2023 16:45:39 +0000 Subject: [PATCH 01/21] test(e2e): fix gantt `todayMarker` tests The gantt diagram that were supposed to test whether `todayMarker off` works wasn't working properly, because `todayMarker on` wasn't working (i.e. the test never failed). I've fixed this issue, and added a test that checks whether `todayMarker on` works. Fixes: https://github.com/mermaid-js/mermaid/issues/4198 --- cypress/integration/rendering/gantt.spec.js | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index c0156eee3..b01a15809 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -133,6 +133,24 @@ describe('Gantt diagram', () => { ); }); + it('should default to showing today marker', () => { + // This test only works if the environment thinks today is 1010-10-10 + imgSnapshotTest( + ` + gantt + title Show today marker (vertical line should be visible) + dateFormat YYYY-MM-DD + axisFormat %d + %% Should default to being on + %% todayMarker on + section Section1 + Yesterday: 1010-10-09, 1d + Today: 1010-10-10, 1d + `, + {} + ); + }); + it('should hide today marker', () => { imgSnapshotTest( ` @@ -142,7 +160,8 @@ describe('Gantt diagram', () => { axisFormat %d todayMarker off section Section1 - Today: 1, -1h + Yesterday: 1010-10-09, 1d + Today: 1010-10-10, 1d `, {} ); @@ -157,7 +176,8 @@ describe('Gantt diagram', () => { axisFormat %d todayMarker stroke-width:5px,stroke:#00f,opacity:0.5 section Section1 - Today: 1, -1h + Yesterday: 1010-10-09, 1d + Today: 1010-10-10, 1d `, {} ); From b3b7108d5903ed879e9300abea20dab116002cf5 Mon Sep 17 00:00:00 2001 From: Jeremy Funk Date: Wed, 22 Mar 2023 23:15:54 +0100 Subject: [PATCH 02/21] 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 03/21] 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
+    
+
+ + + diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 73bfcf084..a88a34f19 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -13,7 +13,7 @@ import classDiagramV2 from '../diagrams/class/classDetector-V2'; import state from '../diagrams/state/stateDetector'; import stateV2 from '../diagrams/state/stateDetector-V2'; import journey from '../diagrams/user-journey/journeyDetector'; -import error from '../diagrams/error/errorDetector'; +import errorDiagram from '../diagrams/error/errorDiagram'; import flowchartElk from '../diagrams/flowchart/elk/detector'; import timeline from '../diagrams/timeline/detector'; import mindmap from '../diagrams/mindmap/detector'; @@ -28,6 +28,9 @@ export const addDiagrams = () => { // This is added here to avoid race-conditions. // We could optimize the loading logic somehow. hasLoadedDiagrams = true; + registerDiagram('error', errorDiagram, (text) => { + return text.toLowerCase().trim() === 'error'; + }); registerDiagram( '---', // --- diagram type may appear if YAML front-matter is not parsed correctly @@ -57,7 +60,6 @@ export const addDiagrams = () => { ); // Ordering of detectors is important. The first one to return true will be used. registerLazyLoadedDiagrams( - error, c4, classDiagramV2, classDiagram, diff --git a/packages/mermaid/src/diagrams/error/errorDetector.ts b/packages/mermaid/src/diagrams/error/errorDetector.ts deleted file mode 100644 index 2bdcd7028..000000000 --- a/packages/mermaid/src/diagrams/error/errorDetector.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types'; - -const id = 'error'; - -const detector: DiagramDetector = (text) => { - return text.toLowerCase().trim() === 'error'; -}; - -const loader = async () => { - const { diagram } = await import('./errorDiagram'); - return { id, diagram }; -}; - -const plugin: ExternalDiagramDefinition = { - id, - detector, - loader, -}; - -export default plugin; diff --git a/packages/mermaid/src/diagrams/error/errorDiagram.ts b/packages/mermaid/src/diagrams/error/errorDiagram.ts index d081e1028..7b9f18c3b 100644 --- a/packages/mermaid/src/diagrams/error/errorDiagram.ts +++ b/packages/mermaid/src/diagrams/error/errorDiagram.ts @@ -19,3 +19,5 @@ export const diagram: DiagramDefinition = { // no op }, }; + +export default diagram; diff --git a/packages/mermaid/src/diagrams/error/errorRenderer.ts b/packages/mermaid/src/diagrams/error/errorRenderer.ts index 60877cb8d..046bcfdcf 100644 --- a/packages/mermaid/src/diagrams/error/errorRenderer.ts +++ b/packages/mermaid/src/diagrams/error/errorRenderer.ts @@ -4,15 +4,13 @@ import { select } from 'd3'; import { log } from '../../logger'; import { getErrorMessage } from '../../utils'; -let conf = {}; - /** * Merges the value of `conf` with the passed `cnf` * * @param cnf - Config to merge */ -export const setConf = function (cnf: any) { - conf = { ...conf, ...cnf }; +export const setConf = function () { + // no-op }; /** @@ -78,7 +76,7 @@ export const draw = (_text: string, id: string, mermaidVersion: string) => { .attr('y', 250) .attr('font-size', '150px') .style('text-anchor', 'middle') - .text('Syntax error in graph'); + .text('Syntax error in text'); g.append('text') // text label for the x axis .attr('class', 'error-text') .attr('x', 1250) diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index dba629477..520bf8b4e 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -534,6 +534,10 @@ const render = async function ( attachFunctions(); + if (parseEncounteredException) { + throw parseEncounteredException; + } + // ------------------------------------------------------------------------------- // Remove the temporary HTML element if appropriate const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector; @@ -542,10 +546,6 @@ const render = async function ( node.remove(); } - if (parseEncounteredException) { - throw parseEncounteredException; - } - return { svg: svgCode, bindFunctions: diag.db.bindFunctions, From 7739302ee80961f27a30d2c39d5494ca94fad54d Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 30 Mar 2023 23:28:41 +0530 Subject: [PATCH 17/21] fix uncaughexception in tests --- cypress/integration/rendering/errorDiagram.spec.js | 9 +++++++++ cypress/platform/viewer.js | 6 ++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cypress/integration/rendering/errorDiagram.spec.js b/cypress/integration/rendering/errorDiagram.spec.js index 89b8403a4..e837565d3 100644 --- a/cypress/integration/rendering/errorDiagram.spec.js +++ b/cypress/integration/rendering/errorDiagram.spec.js @@ -1,6 +1,15 @@ import { imgSnapshotTest } from '../../helpers/util'; describe('Error Diagrams', () => { + beforeEach(() => { + cy.on('uncaught:exception', (err) => { + expect(err.message).to.include('Parse error'); + // return false to prevent the error from + // failing this test + return false; + }); + }); + it('should render a simple ER diagram', () => { imgSnapshotTest( ` diff --git a/cypress/platform/viewer.js b/cypress/platform/viewer.js index 2e1093519..99533192d 100644 --- a/cypress/platform/viewer.js +++ b/cypress/platform/viewer.js @@ -47,7 +47,6 @@ const contentLoaded = async function () { await mermaid2.registerExternalDiagrams([externalExample]); mermaid2.initialize(graphObj.mermaid); await mermaid2.run(); - markRendered(); } }; @@ -123,7 +122,6 @@ const contentLoadedApi = async function () { bindFunctions(div); } } - markRendered(); }; if (typeof document !== 'undefined') { @@ -135,10 +133,10 @@ if (typeof document !== 'undefined') { function () { if (this.location.href.match('xss.html')) { this.console.log('Using api'); - void contentLoadedApi(); + void contentLoadedApi().finally(markRendered); } else { this.console.log('Not using api'); - void contentLoaded(); + void contentLoaded().finally(markRendered); } }, false From d16894daf492acb50d028a089c50b6381d21fddf Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 31 Mar 2023 00:18:53 +0530 Subject: [PATCH 18/21] test: add space before init --- .../mermaid/src/diagram-api/comments.spec.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/mermaid/src/diagram-api/comments.spec.ts b/packages/mermaid/src/diagram-api/comments.spec.ts index 2435db0a0..4357b25c3 100644 --- a/packages/mermaid/src/diagram-api/comments.spec.ts +++ b/packages/mermaid/src/diagram-api/comments.spec.ts @@ -29,6 +29,7 @@ graph TD %% This is another comment %%{init: {'theme': 'forest'}}%% +%%{ init: {'theme': 'space before init'}}%% %%{init: {'theme': 'space after ending'}}%% graph TD A-->B @@ -37,17 +38,18 @@ graph TD %% This is a comment `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " + " - %%{init: {'theme': 'forest'}}%% - %%{init: {'theme': 'space after ending'}}%% - graph TD - A-->B + %%{init: {'theme': 'forest'}}%% + %%{ init: {'theme': 'space before init'}}%% + %%{init: {'theme': 'space after ending'}}%% + graph TD + A-->B - B-->C + B-->C - " - `); + " + `); }); it('should remove indented comments', () => { From 1945a629900c39877c4de123244c205e23509d3c Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 31 Mar 2023 00:25:33 +0530 Subject: [PATCH 19/21] fix: trimStart to text --- .../mermaid/src/diagram-api/comments.spec.ts | 22 +++++++++---------- packages/mermaid/src/diagram-api/comments.ts | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/mermaid/src/diagram-api/comments.spec.ts b/packages/mermaid/src/diagram-api/comments.spec.ts index 4357b25c3..366ba119c 100644 --- a/packages/mermaid/src/diagram-api/comments.spec.ts +++ b/packages/mermaid/src/diagram-api/comments.spec.ts @@ -14,13 +14,13 @@ graph TD %% This is a comment `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " + " - graph TD - A-->B + graph TD + A-->B - " - `); + " + `); }); it('should keep init statements when removing comments', () => { @@ -61,12 +61,12 @@ graph TD C-->D `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " - graph TD - A-->B + " + graph TD + A-->B - C-->D - " - `); + C-->D + " + `); }); }); diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts index 19fafe15c..2ee6232de 100644 --- a/packages/mermaid/src/diagram-api/comments.ts +++ b/packages/mermaid/src/diagram-api/comments.ts @@ -4,5 +4,5 @@ * @returns cleaned text */ export const cleanupComments = (text: string): string => { - return text.replace(/^\s*%%(?!{)[^\n]+/gm, ''); + return text.trimStart().replace(/^\s*%%(?!{)[^\n]+/gm, ''); }; From 006da82470c59989f7ce93968e9f773d2b54d164 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 31 Mar 2023 00:35:56 +0530 Subject: [PATCH 20/21] fix: Remove comment line completely --- .../mermaid/src/diagram-api/comments.spec.ts | 44 ++++++++++++++----- packages/mermaid/src/diagram-api/comments.ts | 2 +- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/mermaid/src/diagram-api/comments.spec.ts b/packages/mermaid/src/diagram-api/comments.spec.ts index 366ba119c..a2c896079 100644 --- a/packages/mermaid/src/diagram-api/comments.spec.ts +++ b/packages/mermaid/src/diagram-api/comments.spec.ts @@ -14,11 +14,8 @@ graph TD %% This is a comment `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " - - graph TD + "graph TD A-->B - " `); }); @@ -38,16 +35,13 @@ graph TD %% This is a comment `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " - - %%{init: {'theme': 'forest'}}%% + "%%{init: {'theme': 'forest'}}%% %%{ init: {'theme': 'space before init'}}%% %%{init: {'theme': 'space after ending'}}%% graph TD A-->B B-->C - " `); }); @@ -61,11 +55,39 @@ graph TD C-->D `; expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B + C-->D " - graph TD - A-->B + `); + }); - C-->D + it('should remove empty newlines from start', () => { + const text = ` + + + + +%% This is a comment +graph TD + A-->B +`; + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B + " + `); + }); + + it('should remove comments at end of text with no newline', () => { + const text = ` +graph TD + A-->B +%% This is a comment`; + + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B " `); }); diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts index 2ee6232de..be39b0a0f 100644 --- a/packages/mermaid/src/diagram-api/comments.ts +++ b/packages/mermaid/src/diagram-api/comments.ts @@ -4,5 +4,5 @@ * @returns cleaned text */ export const cleanupComments = (text: string): string => { - return text.trimStart().replace(/^\s*%%(?!{)[^\n]+/gm, ''); + return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, ''); }; From f9c0f1d46f412b18409cc4b136e84efa625f1ab4 Mon Sep 17 00:00:00 2001 From: knsv Date: Fri, 31 Mar 2023 06:46:34 +0000 Subject: [PATCH 21/21] Update docs --- docs/config/setup/modules/mermaidAPI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 5a5a63786..9bc6d3056 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:659](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L659) +[mermaidAPI.ts:660](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L660) ## Functions