Automatic Gantt Chart from Obsidian Tasks & Dataview

Hey there @boomshakalaka

Since the previous posts on this subject, I made this into a CustomJS user script. I find it more convenient. I call the script from within my daily note template like so:

let today = "{{date:YYYY-MM-DD}}";
let tomorrow = "{{date+1:YYYY-MM-DD}}";
let showDone = dv.current().showDone;

function checkAndExecuteGantt() {
    const { dailyGantt } = customJS || {};
    if (dailyGantt && dailyGantt.drawGantt) {
        let parsedToday = new Date(today);
        if (parsedToday.getDay() != 0) {
            dailyGantt.drawGantt(dv, today, tomorrow, moment, showDone);
        }
    } else {
        setTimeout(checkAndExecuteGantt, 500);
    }
}
checkAndExecuteGantt();

and here is the script itself:

class dailyGantt{

	isValidDate(d) {
		return d instanceof Date && !isNaN(d);
	}

	createDateFromString(dateStr) {
		if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr.trim())) {
			console.error("Date string format is incorrect:", dateStr);
			return null;
		}
		const [year, month, day] = dateStr.split('-').map(Number);
		const date = new Date(year, month - 1, day); // month is zero-indexed, day is one-indexed
		if (!this.isValidDate(date)) {
			console.error ("function createDateFromString() — Failed to create a valid date from:", dateStr, "— returning null");
			return null;
		}
		return date;
	}

    extractDate(emoji, taskText) {
        const start = taskText.indexOf(emoji);
         if (start < 0) {
            return "";
            }
            const match = taskText.slice(start + 1).match(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))(\s|$)/);
            return match ? match[0] : "";
        }
        
    textParser(taskText, noteCreationDate) {
        const emojis = ["📅", "⏳", "🛫", "➕", "✅", "⏫", "🔼", "🔽"];
        const DueText = this.extractDate("📅", taskText);
        const scheduledText = this.extractDate("⏳", taskText);
        const startText = this.extractDate("🛫", taskText);
        let addText = this.extractDate("➕", taskText);
        const doneText = this.extractDate("✅", taskText);

        if (addText === "") {
            addText = noteCreationDate;
        }
    
        let h = taskText.indexOf("⏫");
        let m = taskText.indexOf("🔼");
        let l = taskText.indexOf("🔽");

        let PriorityText="";
        if(h>=0){
            PriorityText="High";
        }
        if(m>=0){
            PriorityText="Medium";
        }
        if(l>=0){
            PriorityText="Low";
        }
        const emojisIndex = emojis.map(emoji => taskText.indexOf(emoji)).filter(index => index >= 0);
        let words;
        if (emojisIndex.length > 0) {
            words = taskText.slice(0, Math.min(...emojisIndex)).split(" ");
        } else {
            words = taskText.split(" ");
        }
        words = words.filter((word) => (word) !== "#task");
        let newWords = words.map(
            (word) => word.startsWith("#") ? `{${word.slice(1)}}` : word);
        let nameText = newWords.join(" ");
        return {
            add: addText,
            done: doneText,
            due: DueText,
            name: nameText,
            priority: PriorityText,
            scheduled: scheduledText,
            start: startText
        };
    }
    
    loopGantt (args){
        const { pageArray, showDone, today, tomorrow, moment } = args;
        let queryGlobal = "";
        let i;
        for (i = 0; i < pageArray.length; i+=1) {
            let taskQuery = "";
            if (!pageArray[i].file.tasks || pageArray[i].file.tasks.length === 0) { 
                continue;
            }
            let taskArray = pageArray[i].file.tasks;
            let taskObjs = [];
            let noteCreationDate = moment(pageArray[i].file.cday).format('YYYY-MM-DD');
            let j;
            for (j = 0; j < taskArray.length; j+=1){

                taskObjs[j] = this.textParser(taskArray[j].text, noteCreationDate);
                let theTask = taskObjs[j];
                let monthLater = new Date(today);
                monthLater.setDate(monthLater.getDate() + 30);
                monthLater = monthLater.toISOString().slice(0, 10);
                let monthBefore = new Date(today);
                monthBefore.setDate(monthBefore.getDate() + 30);
                monthBefore = monthBefore.toISOString().slice(0, 10);

				monthLater = this.createDateFromString(monthLater);
				if (!monthLater) {
					console.error("Failed to create a valid date from monthLater:", monthLater);
				}

                if (theTask.name === "") continue; 
                if (!showDone && theTask.done) continue;
				if (theTask.name.includes("⛔")) continue;

                let taskName = theTask.name
                    .replace(/:/g, '') // Removes colons
                    .replace(/\(http[^\)]+\)/g, '') // Removes anything that starts with "(http" and ends with ")"
                    .replace(/\(file[^\)]+\)/g, '') // Removes anything that starts with "(http" and ends with ")"
                    .replace(/\(obsidian[^\)]+\)/g, '') // Removes anything that starts with "(http" and ends with ")"
                    .replace(/\[\[[^\|]+\|/g, '') // Removes wiki link aliases "[[...|"
                    .replace(/\]\]/g, '') // Removes "]]"
                    .replace(/\[|\]/g, '') // Removes "[" and "]"
                    .replace(/\(file[^\)]+\)/g, ''); // Removes anything that starts with "(file" and ends with ")"

                let startDate = theTask.start || theTask.scheduled || theTask.add || noteCreationDate || today;
				let newStartDate = this.createDateFromString(startDate);
				if (!newStartDate) {
					console.error("Failed to create a valid date from startDate: ", startDate);
				} else {
					startDate = newStartDate;
				}

				if (startDate > monthLater) continue;
				if (startDate < monthBefore) continue;

                let endDate = theTask.done || theTask.due || theTask.scheduled;
                if (!endDate) {
                    if (startDate >= today) {
                        let weekLater = new Date(startDate);
                        weekLater.setDate(weekLater.getDate() + 7);
                        endDate = weekLater.toISOString().slice(0, 10);
						endDate = this.createDateFromString(endDate);
						if (!endDate) {
							console.error("Failed to create a valid date from weekLater:", weekLater);
						}

                    } else {
                        endDate = tomorrow;
   
						endDate = this.createDateFromString(endDate);
						if (!endDate) {
							console.error("Failed to create a valid date from tomorrow:", tomorrow);
						}
					}
                }
                
                // Handling tasks with only a due date and no start date
                if (!theTask.start && !theTask.scheduled && theTask.due) {
                    let weekBefore = new Date(theTask.due);
                    weekBefore.setDate(weekBefore.getDate() - 7);  
                    startDate = weekBefore.toISOString().slice(0, 10);
					newStartDate = this.createDateFromString(startDate);
					if (!newStartDate) {
						console.error("Failed to create a valid date from weekBefore:", weekBefore);
					} else {
						startDate = newStartDate;
					}

                }

		if (endDate == startDate) {
		    let weekLater = new Date(startDate);
		    weekLater.setDate(weekLater.getDate() + 7);
		    endDate = weekLater.toISOString().slice(0, 10);
   
			let newEndDate = this.createDateFromString(endDate);
			if (!newEndDate) {
				console.error("Failed to create a valid date from weekLater:", weekLater);
			} else {
				endDate = newEndDate;
			}
	    }
                        
                // mise des tâches au format mermaid
                if (theTask.due){
                    if (theTask.due < today){
                        taskQuery += taskName  + `    :crit, ` + startDate + `, ` + endDate + `\n\n`;
                    } else {
                        taskQuery += taskName  + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                    }
                } else if (theTask.scheduled){
                    if (startDate >= today){
                        taskQuery += taskName + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                    } else {
                        taskQuery += taskName + `    :inactive, ` + startDate + `, ` + endDate + `\n\n`;
                    }
                } else {
                    taskQuery += taskName  + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                }
            }
            queryGlobal += taskQuery;
        }
        return queryGlobal;
    }
    
    drawGantt(dv, today, tomorrow, moment, showDone) {
        const Mermaid = `gantt
            dateFormat  YYYY-MM-DD
            axisFormat %b\n %d
            `;

        // Get all dashboard pages with status "en cours" (ongoing)
        let dashboardPages = dv.pages().where(p => (p.status == "en cours" || p.status == "permanent") && p.type == "dashboard");

        // Extract tagNames from these dashboard pages
        let tagNames = dashboardPages.map(page => page.tagName);

        // Get all pages that contain tasks with the extracted tagNames and #task
        let filteredPages = [];
        for (let page of dv.pages()) {
            let tasks = page.file.tasks.filter(t => 
                t.status != "-" &&
                t.text.includes("#task") && 
                tagNames.some(tag => t.text.includes(tag)) &&
                !t.text.includes("#someday") &&
                !t.text.includes("#waitingFor") &&
				   !t.text.includes("Revue hebdomadaire")
            );
            if (tasks.length > 0) {
                filteredPages.push({ ...page, file: { ...page.file, tasks: tasks } });
            }
        }
    
        let ganttOutput = this.loopGantt({pageArray:filteredPages, showDone, today, tomorrow, moment});
		// console.log(ganttOutput);
        ganttOutput += "🧘🏻‍♂️ :active, " + today + ", " + today + "\n\n"; // (dummy task to display today's date)
        dv.paragraph("```mermaid\n" + 
            Mermaid + 
            ganttOutput + 
            "\n```");
        // this prints a meta-bind inline field allowing the user to toggle a boolean in the metadata and thus show/hide completed tasks:
        //dv.paragraph(`Montrer les tâches terminées \`INPUT[toggle:showDone]\``);
    }

}


edit: a few corrections brought to the script a few months later
another edit: update to fix date creation logic broken by some change somewhere else + hide blocked tasks

Let me know if you could adapt it to your use case.

Hi everyone, I tried to made the edits such as paths but for some reason I still see this on my screen. I’ve already uploaded my tasks and path to the section so wondering whats the issue here?

Thank you very much,
I’m just afraid I still miss some steps…
What plugins/settings do I need for this?
How do I link to the dailyGantt.js ? just put it in \Templates\templaterjs ?

returns this:

Evaluation Error: ReferenceError: customJS is not defined
    at checkAndExecuteGantt (eval at <anonymous> (plugin:dataview), <anonymous>:6:28)
    at eval (eval at <anonymous> (plugin:dataview), <anonymous>:16:1)
    at DataviewInlineApi.eval (plugin:dataview:18638:16)
    at evalInContext (plugin:dataview:18639:7)
    at asyncEvalInContext (plugin:dataview:18649:32)
    at DataviewJSRenderer.render (plugin:dataview:18670:19)
    at DataviewJSRenderer.onload (plugin:dataview:18260:14)
    at e.load (app://obsidian.md/app.js:1:1147632)
    at DataviewApi.executeJs (plugin:dataview:19198:18)
    at DataviewPlugin.dataviewjs (plugin:dataview:20068:18)
    at eval (plugin:dataview:19967:124)
    at t.initDOM (app://obsidian.md/app.js:1:1539674)
    at t.toDOM (app://obsidian.md/app.js:1:1141789)
    at t.sync (app://obsidian.md/app.js:1:351034)
    at e.sync (app://obsidian.md/app.js:1:332767)
    at app://obsidian.md/app.js:1:373556
    at e.ignore (app://obsidian.md/app.js:1:452767)
    at t.updateInner (app://obsidian.md/app.js:1:373338)
    at t.update (app://obsidian.md/app.js:1:373093)
    at e.update (app://obsidian.md/app.js:1:461415)
    at e.dispatchTransactions (app://obsidian.md/app.js:1:458098)
    at e.dispatch (app://obsidian.md/app.js:1:459981)
    at app://obsidian.md/app.js:1:1556860
1 Like

You have to install CustomJS as well. Then you define a folder in CustomJS’ settings where you put the dailyGantt.js file above (folder relative to your vault root). For testing, you can set the dates in the beginning of the short script to an actual date like so:

let today = "2024-01-23";
let tomorrow = "2024-01-24";

Let me know if it works, because unfortunately it doesn’t for me (I have lots of tasks, but no Gantt is showing up although something is rendered:

grafik

Not sure what to make of this. Will be looking into it some more…

@johe How are your tasks formatted? It looks like the parser function doesn’t work as intended, but the Gantt itself is rendered (the zen emoji is a dummy task to show the current date).

@boomshakalaka That’s right, you need CustomJS. const { dailyGantt } = customJS is how the second script is called from within your note. CustomJS will look for a dailyGantt class inside the files in the CustomJS scripts directory.

You could do without customJS and just put the second script into your note, with a little adaptation. I did this because I didn’t like to have that many lines of code in my daily note when I edit it in source mode.

Hey @Brains4Us,

I’m just a Sunday tinkerer and I really don’t know why it doesn’t work on your setup. Maybe add some log statements along the code to see what’s happening exactly? Can you read any error messages in the console?

Also it seems (from the section title “Choses à faire” that you’re using the old version of the script, did you check the one in my second-to-last message?

@josephtribulat Thanks for your “Sunday tinkering” :slight_smile:, the intended functionality looks like something I was searching for. My tasks are formatted according to the “Tasks” community plugin defaults, e.g.:

* [ ] please do something ➕ 2024-01-25 🛫 2024-01-25 📅 2024-01-29

Maybe the problem is that I don’t include the keyword “#task” in my tasks, so I would have to look for the brackets instead. What would I have to change for this to work?

One thing I did already change in the daily note template, is to adapt the {{date:…} placeholders to the format expected by the Templater plugin, but I don’t think that this is breaking the code:

let today = "<% tp.date.now("YYYY-MM-DD") %>";

In this case I would try deleting this line in the for loop that’s in the drawGantt function:

t.text.includes("#task") &&

I tried this, but unfortunately, it doesn’t work. I also added some dummy tasks with the #task tag, which also weren’t found. Might it have to do with the query dv.current().showDone;? This property is nowhere to be found in the metadata (did you add it in your system?). There is a commented line in the .js file, which looks like it’s supposed to add a toggle for showing the finished tasks to the Gantt diagram (uncommenting this doesn’t work either, however).

Edit: Getting closer, commenting some of the other conditions in the task filter had some visible effects. Will update as soon as I know what the problem was.

Okay, I found it, and the result is now absolutely fabulous:

  1. Had to remove both t.text.includes("#task") && and tagNames.some(tag => t.text.includes(tag)) &&
  2. Had to make sure I didn’t have tasks that have no due date (will try and make this more flexible later, but for now this isn’t imposing too many constraints for me)

One thing I will change in the near future is to sort the tasks by due date (probably just have to add a .sort(...something)somewhere. Thank you again, @josephtribulat, for this fantastic tool!

You’re most welcome, and credit should rather go to @LynnXie00 who did all the hard part at the top of this thread :slight_smile:

As for showDone, you guessed it, it’s a property in my note, that is linked to a switch in the note’s body thanks to the Meta-Bind plugin.

Hi, everyone and thank you for your effort. I tried to follow your guidelines, but unfortunately, it doesn’t work for me.

Below performed steps:

  • Installed CustomJS. Dataview and templater already installed
  • Made a Scripts Folder and placed dailyGantt.js inside. Configured CustomJS to look into that folder to find scripts
  • In my Daily note i placed the dataviewjs query to invoke the script

return this:

Is that a file you downloaded, or did you copy/paste the code from this thread into dailyGantt.js?

The error seems to be some kind of syntax error. My first guess would be if you copy/pasted but ended up with smart quotes instead of regular quotes, or something similar.

I think it would be helpful to see the code in that file. The best thing to do would be to copy/paste it here between backticks.

```
Your dailyGantt.js code
```

Hi @rigmarole thank you for your answer. Probably my bad, having copied/pasted the code from this thread directly into the file.

Below the script:

class dailyGantt{

    extractDate(emoji, taskText) {
        const start = taskText.indexOf(emoji);
         if (start < 0) {
            return "";
            }
            const match = taskText.slice(start + 1).match(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))(\s|$)/);
            return match ? match[0] : "";
        }
        
    textParser(taskText, noteCreationDate) {
        const emojis = ["📅", "⏳", "🛫", "➕", "✅", "⏫", "🔼", "🔽"];
        const DueText = this.extractDate("📅", taskText);
        const scheduledText = this.extractDate("⏳", taskText);
        const startText = this.extractDate("🛫", taskText);
        let addText = this.extractDate("➕", taskText);
        const doneText = this.extractDate("✅", taskText);

        if (addText === "") {
            addText = noteCreationDate;
        }
    
        let h = taskText.indexOf("⏫");
        let m = taskText.indexOf("🔼");
        let l = taskText.indexOf("🔽");

        let PriorityText="";
        if(h>=0){
            PriorityText="High";
        }
        if(m>=0){
            PriorityText="Medium";
        }
        if(l>=0){
            PriorityText="Low";
        }
        const emojisIndex = emojis.map(emoji => taskText.indexOf(emoji)).filter(index => index >= 0);
        let words;
        if (emojisIndex.length > 0) {
            words = taskText.slice(0, Math.min(...emojisIndex)).split(" ");
        } else {
            words = taskText.split(" ");
        }
        words = words.filter((word) => (word) !== "#task");
        let newWords = words.map(
            (word) => word.startsWith("#") ? `[${word.slice(1)}]` : word);
        let nameText = newWords.join(" ");
        return {
            add: addText,
            done: doneText,
            due: DueText,
            name: nameText,
            priority: PriorityText,
            scheduled: scheduledText,
            start: startText
        };
    }
    
    loopGantt (args){
        const { pageArray, showDone, today, tomorrow, moment } = args;
        let queryGlobal = "";
        let i;
        for (i = 0; i < pageArray.length; i+=1) {
            let taskQuery = "";
            if (!pageArray[i].file.tasks || pageArray[i].file.tasks.length === 0) { 
                continue;
            }
            let taskArray = pageArray[i].file.tasks;
            let taskObjs = [];
            let noteCreationDate = moment(pageArray[i].file.cday).format('YYYY-MM-DD');
            let j;
            for (j = 0; j < taskArray.length; j+=1){

                taskObjs[j] = this.textParser(taskArray[j].text, noteCreationDate);
                let theTask = taskObjs[j];
                let monthLater = new Date(today);
                monthLater.setDate(monthLater.getDate() + 30);
                monthLater = monthLater.toISOString().slice(0, 10);

                if (theTask.name === "") continue; 
                if (!showDone && theTask.done) continue;

                let taskName = theTask.name
                    .replace(/:/g, '')
                    .replace(/\(http[^\)]+\)/g, '')
                    .replace(/\[\[[^\|]+\|/g, '')
                    .replace(/\]\]/g, '');
                let startDate = theTask.start || theTask.scheduled || theTask.add || noteCreationDate || today;

                if (startDate >= monthLater) continue;
                // ligne suivante cache les tâches qui n'ont aucune date, uncomment -> la date de création de la tâche ou de la note est employée par défaut

                if (!theTask.start && !theTask.scheduled && !theTask.due) continue;

                let endDate = theTask.done || theTask.due || theTask.scheduled;
                if (!endDate) {
                    if (startDate >= today) {  // If start date is in the future
                        let weekLater = new Date(startDate);
                        weekLater.setDate(weekLater.getDate() + 7);
                        endDate = weekLater.toISOString().slice(0, 10);
                    } else {  // If start date is in the past
                        endDate = tomorrow;
                    }
                }

                if (theTask.due){
                    if (theTask.due < today){
                        taskQuery += taskName  + `    :crit, ` + startDate + `, ` + endDate + `\n\n`;
                    } else {
                        taskQuery += taskName  + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                    }
                } else if (theTask.scheduled){
                    if (startDate <= today){
                        taskQuery += taskName + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                    } else {
                        taskQuery += taskName + `    :inactive, ` + startDate + `, ` + endDate + `\n\n`;
                    }
                } else {
                    taskQuery += taskName  + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                }
            }
            queryGlobal += taskQuery;
        }
        return queryGlobal;
    }
    
    drawGantt(dv, today, tomorrow, moment, showDone) {
        const Mermaid = `gantt
            dateFormat  YYYY-MM-DD
            axisFormat %b\n %d
            `;

        // Get all dashboard pages with status "en cours" (ongoing)
        let dashboardPages = dv.pages().where(p => (p.status == "en cours" || p.status == "permanent") && p.type == "dashboard");

        // Extract tagNames from these dashboard pages
        let tagNames = dashboardPages.map(page => page.tagName);

        // Get all pages that contain tasks with the extracted tagNames and #task
        let filteredPages = [];
        for (let page of dv.pages()) {
            let tasks = page.file.tasks.filter(t => 
                t.status != "-" &&
                !t.text.includes("#someday") &&
                !t.text.includes("waitingFor")
            );
            if (tasks.length > 0) {
                filteredPages.push({ ...page, file: { ...page.file, tasks: tasks } });
            }
        }
    
        let ganttOutput = this.loopGantt({pageArray:filteredPages, showDone, today, tomorrow, moment});
        ganttOutput += "🧘🏻‍♂️ :active, " + today + ", " + today + "\n\n"; // (dummy task to display today's date)
        dv.paragraph("```mermaid\n" + 
            Mermaid + 
            ganttOutput + 
            "\n```");
        // this prints a meta-bind inline field allowing the user to toggle a boolean in the metadata and thus show/hide completed tasks:
        //dv.paragraph(`Montrer les tâches terminées \`INPUT[toggle:showDone]\``);
    }

}

As you noticed the error seems to be some kind of syntax error, according to attached preview.

Thanks a lot

So I think (but I’m not familiar with what this code is doing) that you didn’t put it inside code blocks and say what the language is:

```dataviewjs

The code goes here

```

If you copy the triple backticks here, the forum doesn’t show them, and so people copying the code miss that part. I’m showing them by putting them surrounded by 4 backticks. And I’ll show that by using 5 backticks.

````
```dataviewjs

Code goes here

```
````
1 Like

I am close to fix the problem, i pasted the script into Visual studio code saved as .js file and dropped into my vault, now it works.

Regarding the dataview code to trigger Mermaid inside my Daily note:

```dataviewjs
let today = "{{date:YYYY-MM-DD}}";
let tomorrow = "{{date+1d:YYYY-MM-DD}}";
let showDone = dv.current().showDone;
function checkAndExecuteGantt() {
    const { dailyGantt } = customJS || {};
    if (dailyGantt && dailyGantt.drawGantt) {
        let parsedToday = new Date(today);
        if (parsedToday.getDay() != 0) {
            dailyGantt.drawGantt(dv, today, tomorrow, moment, showDone);
        }
    } else {
        setTimeout(checkAndExecuteGantt, 500);
    }
}
checkAndExecuteGantt();
```

I receive this error

Modifying the dates in the beginning of the short script seems to work:

let today = "2024-01-30";
let tomorrow = "2024-01-31";

I am still a newbie and i am trying to deepen the problem, any suggestion appreciated

I have update, according to my needs, the two code blocks as shown below, now it works:

DailyGantt.js

class dailyGantt {

    extractDate(emoji, taskText) {
        const start = taskText.indexOf(emoji);
        if (start < 0) {
            return "";
        }
        const match = taskText.slice(start + 1).match(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))(\s|$)/);
        return match ? match[0] : "";
    }

    textParser(taskText, noteCreationDate) {
        const emojis = ["📅", "⏳", "🛫", "➕", "✅", "⏫", "🔼", "🔽"];
        const DueText = this.extractDate("📅", taskText);
        const scheduledText = this.extractDate("⏳", taskText);
        const startText = this.extractDate("🛫", taskText);
        let addText = this.extractDate("➕", taskText);
        const doneText = this.extractDate("✅", taskText);

        if (addText === "") {
            addText = noteCreationDate;
        }

        let h = taskText.indexOf("⏫");
        let m = taskText.indexOf("🔼");
        let l = taskText.indexOf("🔽");

        let PriorityText = "";
        if (h >= 0) {
            PriorityText = "High";
        }
        if (m >= 0) {
            PriorityText = "Medium";
        }
        if (l >= 0) {
            PriorityText = "Low";
        }
        const emojisIndex = emojis.map(emoji => taskText.indexOf(emoji)).filter(index => index >= 0);
        let words;
        if (emojisIndex.length > 0) {
            words = taskText.slice(0, Math.min(...emojisIndex)).split(" ");
        } else {
            words = taskText.split(" ");
        }
        words = words.filter((word) => (word) !== "#task");
        let newWords = words.map(
            (word) => word.startsWith("#") ? `[${word.slice(1)}]` : word);
        let nameText = newWords.join(" ");
        return {
            add: addText,
            done: doneText,
            due: DueText,
            name: nameText,
            priority: PriorityText,
            scheduled: scheduledText,
            start: startText
        };
    }

    loopGantt(args) {
        const { pageArray, showDone, today, tomorrow, moment } = args;
        let queryGlobal = "";
        let i;
        for (i = 0; i < pageArray.length; i += 1) {
            let taskQuery = "";
            if (!pageArray[i].file.tasks || pageArray[i].file.tasks.length === 0) {
                continue;
            }
            let taskArray = pageArray[i].file.tasks;
            let taskObjs = [];
            let noteCreationDate = moment(pageArray[i].file.cday).format('YYYY-MM-DD');
            let j;
            for (j = 0; j < taskArray.length; j += 1) {

                taskObjs[j] = this.textParser(taskArray[j].text, noteCreationDate);
                let theTask = taskObjs[j];
                let monthLater = new Date(today);
                monthLater.setDate(monthLater.getDate() + 30);

                if (monthLater instanceof Date && !isNaN(monthLater)) {
                    monthLater = monthLater.toISOString().slice(0, 10);
                } else {
                    console.error("Invalid monthLater:", monthLater);
                    continue;
                }

                if (theTask.name === "") continue;
                if (!showDone && theTask.done) continue;

                let taskName = theTask.name
                    .replace(/:/g, '')
                    .replace(/\(http[^\)]+\)/g, '')
                    .replace(/\[\[[^\|]+\|/g, '')
                    .replace(/\]\]/g, '');
                let startDate = theTask.start || theTask.scheduled || theTask.add || noteCreationDate || today;

                if (startDate >= monthLater) continue;

                if (!theTask.start && !theTask.scheduled && !theTask.due) continue;

                let endDate = theTask.done || theTask.due || theTask.scheduled;
                if (!endDate) {
                    if (startDate >= today) {
                        let weekLater = new Date(startDate);
                        weekLater.setDate(weekLater.getDate() + 7);
                        endDate = weekLater.toISOString().slice(0, 10);
                    } else {
                        endDate = tomorrow;
                    }
                }

                if (theTask.due) {
                    if (theTask.due < today) {
                        taskQuery += taskName + `    :crit, ` + startDate + `, ` + endDate + `\n\n`;
                    } else {
                        taskQuery += taskName + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                    }
                } else if (theTask.scheduled) {
                    if (startDate <= today) {
                        taskQuery += taskName + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                    } else {
                        taskQuery += taskName + `    :inactive, ` + startDate + `, ` + endDate + `\n\n`;
                    }
                } else {
                    taskQuery += taskName + `    :active, ` + startDate + `, ` + endDate + `\n\n`;
                }
            }
            queryGlobal += taskQuery;
        }
        return queryGlobal;
    }

    drawGantt(dv, today, tomorrow, moment, showDone) {
        const Mermaid = `gantt
            dateFormat  YYYY-MM-DD
            axisFormat %b\n %d
            `;

        // Get all dashboard pages with status "en cours" (ongoing)
        let dashboardPages = dv.pages().where(p => (p.status == "en cours" || p.status == "permanent") && p.type == "dashboard");

        // Extract tagNames from these dashboard pages
        let tagNames = dashboardPages.map(page => page.tagName);

        // Get all pages that contain tasks with the extracted tagNames and #task
        let filteredPages = [];
        for (let page of dv.pages()) {
            let tasks = page.file.tasks.filter(t =>
                t.status != "-" &&
                !t.text.includes("#someday") &&
                !t.text.includes("waitingFor")
            );
            if (tasks.length > 0) {
                filteredPages.push({ ...page, file: { ...page.file, tasks: tasks } });
            }
        }

        let ganttOutput = this.loopGantt({ pageArray: filteredPages, showDone, today, tomorrow, moment });
        ganttOutput += "🧘🏻‍♂️ :active, " + today + ", " + today + "\n\n"; // (dummy task to display today's date)
        dv.paragraph("```mermaid\n" +
            Mermaid +
            ganttOutput +
            "\n```");
        // this prints a meta-bind inline field allowing the user to toggle a boolean in the metadata and thus show/hide completed tasks:
        //dv.paragraph(`Montrer les tâches terminées \`INPUT[toggle:showDone]\``);
    }

}

Dataview snippet for daily note

```dataviewjs
let today = new Date().toISOString().slice(0, 10); 
let tomorrow = new Date(); 
tomorrow.setDate(tomorrow.getDate() + 1); 
tomorrow = tomorrow.toISOString().slice(0, 10);
let showDone = dv.current().showDone;
function checkAndExecuteGantt() {
    const { dailyGantt } = customJS || {};
    if (dailyGantt && dailyGantt.drawGantt) {
        let parsedToday = new Date(today);
        if (parsedToday.getDay() != 0) {
            dailyGantt.drawGantt(dv, today, tomorrow, moment, showDone);
        }
    } else {
        setTimeout(checkAndExecuteGantt, 500);
    }
}
checkAndExecuteGantt();
```

I hope it can be of help to others too

Need help because your script doesn’t work for the selected dates. For duration it works well. I’m including a screenshot.

axis_format:: %d-%m

```dataviewjs
function textParser(taskText){//input text,return object

let du= taskText.indexOf("⏱️")
let durText = "";
if (du>0){
let i=taskText.slice(du).search(/\d+(d|w|m)/);
	durText=taskText.substr(du+i, 3)

}
 
let miletext = taskText.indexOf("🚩") > -1 ? 1 : 0;

let d = taskText.indexOf("📅");
let DueText="";
if(d>=0){
let i=taskText.slice(d).search(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/);
	DueText=taskText.substr(d+i,10);
} 







    let sch = taskText.indexOf("⏳");
    let scheduledText="";
    if(sch>0){
        let i=taskText.slice(sch).search(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/);
        scheduledText=taskText.substr(sch+i,10);
    } 

    let st = taskText.indexOf("🛫");
    let startText="";
    if(st>0){
        let i=taskText.slice(st).search(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/);
        startText=taskText.substr(st+i,10);
    } 
    
    let h = taskText.indexOf("⏫");
    let m = taskText.indexOf("🔼");
    let l =taskText.indexOf("🔽");
    let PriorityText="";
    if(h>0){
        PriorityText="High";
    } 
    if(m>0){
        PriorityText="Medium";
    } 
    if(l>0){
        PriorityText="Low";
    } 
    const emojisIndex= [d,sch,st,h,m,l,du]
    const presentEmojiIndex= emojisIndex.filter(x => x > 0);

    let nameText=taskText.slice(0,Math.min(...presentEmojiIndex)).trim();


    //console.log(taskText,Math.min(...presentEmojiIndex))
	return {    
		name: nameText,
		due: DueText,
        start:startText,
        scheduled:scheduledText,
        priority:PriorityText,
        duration:durText,
	 miletext: miletext
	}
}

function loopGantt (pageArray){
	





	let querySections=``;
	let today = new Date().toISOString().slice(0, 10)
	for (let i = 0; i < pageArray.length; i++) {  
		let taskQuery=``;
		var taskArray = pageArray[i].file.tasks;





//parse name, due, start, completion, scheduled,priority from task text to objects
		var compObjs = pageArray[i].file.tasks.completed
		var completionArray = [];
		for (let s=0; s< compObjs.length;s++){		
			completionArray[s] =compObjs[s]}



		var taskObjs=[];
		for (let j=0; j< taskArray.length;j++){		
			taskObjs[j] = textParser(taskArray[j].text)

		}

 

        //determine the mermaid task parameters
		for (let j=0; j< taskObjs.length;j++){		
			let theTask = taskObjs[j];
  



				// create stats variable 

function getLastLesserIndex(arr, currentIndex) {
  let currentValue = arr[currentIndex];
  for (let i = currentIndex - 1; i >= 0; i--) {
    if (arr[i] < currentValue) {
      return i;
    }
  }
  return -1;
}

let SecNum = "sect" + (i+1)+ "-"


let taskNum= "task" + (j+1) +", "

			let Ind = pageArray[i].file.tasks.position.start.col
			



let IndUp = getLastLesserIndex(Ind,j) 


let aft = ""
if ((taskArray[j].parent != null) && (IndUp > 0)){aft+= "after "+ SecNum+ "task"+(IndUp+1)} else if(taskArray[j].parent != null) {aft+= "after "+ SecNum+ "task"+(j)}  else {aft +="" }



var stats = ""

if (completionArray[j] == true ){
stats += "done, "
} else { stats+= "active, "}
	
// test stuff
var critStat = ""

if (theTask.priority === "High" ) {critStat = "crit"+ ", "} else { critStat = ""}

var start = ""
if (taskArray[j].parent != null) {start += aft +","} else{start+= theTask.start+","}


var end = ""
if(taskArray[j].duration = null) {end += theTask.duration} 
	else {end += theTask.due}

let mile = ""
if (taskObjs[j].miletext>0) {mile+= "milestone, "}


//

					taskQuery+= theTask.name + `    : `  +mile+ critStat+ stats+ SecNum+taskNum+ start+ end+ theTask.duration+`\n\n`;		
					
						
				};
		
		
		
		
		querySections+= `section  `+pageArray[i].file.name+`\n\n`+taskQuery;
		
	};
	return querySections
}


const Mermaid = `gantt

    title Gannt Charts (v0.4)
    
   
    

axisFormat  `+ dv.current().axis_format +


  ` \n `;



// set the path of your project folder below
dv.paragraph('```mermaid\n' + Mermaid + loopGantt(dv.pages('#gannt'))+ '\n```');




// render gannt page with checkboxes

//digonstic rendiering. uncomment to get a render of the merimaid text and otehr diagnostic stuff

//dv.paragraph('```' + Mermaid + loopGantt(dv.pages('#gannt'))+ '\n ```');

So sorry for the delay, I really need to log in more, lol.

Not entierly sure why that is happinging. But it works fine on my current version of the script.

the update take more inputs from feilds on the page with the gannt chart to allow for adjsuting the Axis ticks based on the format descrbed here.

in the example below, “tick amount” holds the number and “tickScale” holds the day/week/month parts of the pattern Described in the link. along the way i must have halso fixed whatever was causing your issue

I hope this helps!


Gannt config
axis_format:: %m-%d-%y
axis tick
tickAmount:: 6
tickScale:: month

Gannt render

```dataviewjs
function textParser(taskText){//input text,return object

let du= taskText.indexOf("⏱️")
let durText = "";
if (du>0){
let i=taskText.slice(du).search(/\d+(d|w|m)/);
	durText=taskText.substr(du+i, 3)

}
 
let miletext = taskText.indexOf("🚩") > -1 ? 1 : 0;
 
 



    let d = taskText.indexOf("📅");
    let DueText="";
    if(d>=0){
        let i=taskText.slice(d).search(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/);
        DueText=taskText.substr(d+i,10);
    } 







    let sch = taskText.indexOf("⏳");
    let scheduledText="";
    if(sch>0){
        let i=taskText.slice(sch).search(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/);
        scheduledText=taskText.substr(sch+i,10);
    } 

    let st = taskText.indexOf("🛫");
    let startText="";
    if(st>0){
        let i=taskText.slice(st).search(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/);
        startText=taskText.substr(st+i,10);
    } 
    
    let h = taskText.indexOf("⏫");
    let m = taskText.indexOf("🔼");
    let l =taskText.indexOf("🔽");
    let PriorityText="";
    if(h>0){
        PriorityText="High";
    } 
    if(m>0){
        PriorityText="Medium";
    } 
    if(l>0){
        PriorityText="Low";
    } 
    const emojisIndex= [d,sch,st,h,m,l,du]
    const presentEmojiIndex= emojisIndex.filter(x => x > 0);

    let nameText=taskText.slice(0,Math.min(...presentEmojiIndex)).trim();


    //console.log(taskText,Math.min(...presentEmojiIndex))
	return {    
		name: nameText,
		due: DueText,
        start:startText,
        scheduled:scheduledText,
        priority:PriorityText,
        duration:durText,
	 miletext: miletext
	}
}

function loopGantt (pageArray){
	





	let querySections=``;
	let today = new Date().toISOString().slice(0, 10)
	for (let i = 0; i < pageArray.length; i++) {  
		let taskQuery=``;
		var taskArray = pageArray[i].file.tasks;





//parse name, due, start, completion, scheduled,priority from task text to objects
		var compObjs = pageArray[i].file.tasks.completed
		var completionArray = [];
		for (let s=0; s< compObjs.length;s++){		
			completionArray[s] =compObjs[s]}



		var taskObjs=[];
		for (let j=0; j< taskArray.length;j++){		
			taskObjs[j] = textParser(taskArray[j].text)

		}

 

        //determine the mermaid task parameters
		for (let j=0; j< taskObjs.length;j++){		
			let theTask = taskObjs[j];
  



				// create stats variable 

function getLastLesserIndex(arr, currentIndex) {
  let currentValue = arr[currentIndex];
  for (let i = currentIndex - 1; i >= 0; i--) {
    if (arr[i] < currentValue) {
      return i;
    }
  }
  return -1;
}

let SecNum = "sect" + (i+1)+ "-"


let taskNum= "task" + (j+1) +", "

			let Ind = pageArray[i].file.tasks.position.start.col
			



let IndUp = getLastLesserIndex(Ind,j) 


let aft = ""
if ((taskArray[j].parent != null) && (IndUp > 0)){aft+= "after "+ SecNum+ "task"+(IndUp+1)} else if(taskArray[j].parent != null) {aft+= "after "+ SecNum+ "task"+(j)}  else {aft +="" }



var stats = ""

if (completionArray[j] == true ){
stats += "done, "
} else { stats+= "active, "}
	
// test stuff
var critStat = ""

if (theTask.priority === "High" ) {critStat = "crit"+ ", "} else { critStat = ""}

var start = ""
if (taskArray[j].parent != null) {start += aft +","} else{start+= theTask.start+","}


var end = ""
if(taskArray[j].duration = null) {end += theTask.duration} 
	else {end += theTask.due}

let mile = ""
if (taskObjs[j].miletext>0) {mile+= "milestone, "}


//

					taskQuery+= theTask.name + `    : `  +mile+ critStat+ stats+ SecNum+taskNum+ start+ end+ theTask.duration+`\n\n`;		
					
						
				};
		
		
		
		
		querySections+= `section  `+pageArray[i].file.name+`\n\n`+taskQuery;
		
	};
	return querySections
}

let tick1 = dv.current().tickAmount // define first part of tick interval (number) 
let tick2 = dv.current().tickScale // define the scale of axis tick (day, week, month, year) 

const Mermaid = `gantt

    title Gannt Charts (v0.5.5)
    
   
    
 \n ` + `dateFormat YYYY-MM-DD` + ` \n ` +
`axisFormat  `+ dv.current().axis_format + ` \n ` + `tickInterval `  + tick1 + tick2+


  `\n `;



// set the path of your project folder below
dv.paragraph('```mermaid\n' + Mermaid + loopGantt(dv.pages('#gannt and [[]]'))+ '\n```');




// render gannt page with checkboxes

//digonstic rendiering. uncomment to get a render of the merimaid text and otehr diagnostic stuff

//dv.paragraph('```' + Mermaid + loopGantt(dv.pages('#gannt and [[]]'))+ '\n ```');


1 Like

Hello there.
I tried to follow along, since I would also really like to have a GANTT for my tasks. However, I seem to be incapable.
I installed CustomJS and Dataview. But I only get the same red line as @Brains4Us did on Jan 21.
Maybe at some point somebody can provide a short tutorial?
regards

1 Like