# Gantt Chart

**URL:** <https://forum.radzen.com/t/gantt-chart/16684>\
**Category:** Radzen.Blazor Components\
**Created:** [March 11, 2024, 7:44am UTC](https://forum.radzen.com/t/gantt-chart/16684 "2024-03-11T07:44:36Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![Fady1956](https://forum.radzen.com/letter_avatar_proxy/v4/letter/f/bb73d2/32.png) [@Fady1956](https://forum.radzen.com/u/Fady1956)\
**Post date:** [March 11, 2024, 7:44am UTC](https://forum.radzen.com/t/gantt-chart/16684/1 "2024-03-11T07:44:37Z")

</div>

Hello,  
a must have component is a robust Gantt Chart that would have mulitple views including hour, days, month, and year. such a component would be of great assistance.

---

<div class="post-metadata">

**Author:** ![enchev](https://forum.radzen.com/user_avatar/forum.radzen.com/enchev/32/16_2.png) [@enchev](https://forum.radzen.com/u/enchev)\
**Post date:** [March 11, 2024, 9:07am UTC](https://forum.radzen.com/t/gantt-chart/16684/2 "2024-03-11T09:07:19Z")

</div>

Hey @Fady1956,

We don’t have plans for such component however we accept pull requests!

---

<div class="post-metadata">

**Author:** ![Fady1956](https://forum.radzen.com/letter_avatar_proxy/v4/letter/f/bb73d2/32.png) [@Fady1956](https://forum.radzen.com/u/Fady1956)\
**Post date:** [March 11, 2024, 9:22am UTC](https://forum.radzen.com/t/gantt-chart/16684/3 "2024-03-11T09:22:07Z")

</div>

I wish I had the knowledge to create such a component.  
It would have completed your component list beautifully.  
Thanks and Best regards

---

<div class="post-metadata">

**Author:** ![Fady1956](https://forum.radzen.com/letter_avatar_proxy/v4/letter/f/bb73d2/32.png) [@Fady1956](https://forum.radzen.com/u/Fady1956)\
**Post date:** [July 12, 2024, 4:31am UTC](https://forum.radzen.com/t/gantt-chart/16684/4 "2024-07-12T04:31:01Z")

</div>

I know it is not ideal, but I finally created a GanttChart in javascript and I was able to incorporate it in a radzen blazor app.  
this is the code:  
class GanttChart {  
constructor(containerId, options) {  
this.container = document.getElementById(containerId);  
if (!this.container) {  
console.error(`Container with id ${containerId} not found.`);  
return;  
}  
this.dataUrl = options.dataUrl;  
this.headers = options.headers || ['Priority Area', 'Main Topic', 'Expert', 'Days Allocated', 'Start', 'Deadline', '% Completed', 'Status'];  
this.columnWidths = options.columnWidths;  
this.groupBy = options.groupBy || null;  
this.startDate = new Date(options.startDate) || new Date('2024-01-01');  
this.endDate = new Date(options.endDate) || new Date('2024-07-30');

```
    this.init();
}

formatDate(date) {
    let day = date.getDate();
    let month = date.getMonth() + 1;
    let year = date.getFullYear() % 100;
    day = day < 10 ? '0' + day : day;
    month = month < 10 ? '0' + month : month;
    year = year < 10 ? '0' + year : year;
    return `${day}-${month}-${year}`;
};

init() {
    this.fetchData().then(tasks => {
        this.tasks = tasks;
        this.filteredTasks = tasks; // Add this line to keep track of filtered tasks
        this.createGanttChart();
    }).catch(error => {
        console.error('Failed to load data:', error);
    });
}

async fetchData() {
    const response = await fetch(this.dataUrl);
    if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`);
    }
    try {
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Failed to parse JSON:', error);
        console.log('Raw response:', await response.text()); // This line helps to understand what the server actually returned
        throw new Error('Failed to parse JSON.');
    }
}

filterTasks(event) {
    const query = event.target.value.toLowerCase();
    this.filteredTasks = this.tasks.filter(task =>
        Object.values(task).some(value =>
            String(value).toLowerCase().includes(query)
        )
    );
    this.createGanttChart();
}

createGanttChart() {
    // Clear previous contents
    const header = document.getElementById('gantt-header');
    const timelineHeader = document.getElementById('gantt-timeline-header');
    const body = document.getElementById('gantt-body');
    const timelineBody = document.getElementById('gantt-timeline-body');

    header.innerHTML = '';
    timelineHeader.innerHTML = '';
    body.innerHTML = '';
    timelineBody.innerHTML = '';

    // Create search input
    if (!document.querySelector('.search-input')) {
        const searchInput = document.createElement('input');
        searchInput.type = 'text';
        searchInput.placeholder = 'Search...';
        searchInput.className = 'search-input ms-5';
        searchInput.addEventListener('input', this.filterTasks.bind(this));

        // Create buttons for expand and collapse
        const buttonContainer = document.createElement('div');
        buttonContainer.className = 'button-container';

        const expandAllButton = document.createElement('button');
        expandAllButton.textContent = 'Expand All';
        expandAllButton.addEventListener('click', () => {
            document.querySelectorAll('.task-row').forEach(row => row.style.display = 'table-row');
            document.querySelectorAll('.timeline-row').forEach(row => row.style.display = 'table-row');
            document.querySelectorAll('.collapse-icon').forEach(icon => icon.textContent = '▼');
        });
        expandAllButton.className = 'btn btn-outline-secondary ms-5';
        const collapseAllButton = document.createElement('button');
        collapseAllButton.textContent = 'Collapse All';
        collapseAllButton.addEventListener('click', () => {
            document.querySelectorAll('.task-row').forEach(row => row.style.display = 'none');
            document.querySelectorAll('.timeline-row').forEach(row => row.style.display = 'none');
            document.querySelectorAll('.collapse-icon').forEach(icon => icon.textContent = '►');
        });
        collapseAllButton.className = 'btn btn-outline-warning ms-5';
        buttonContainer.appendChild(searchInput);
        buttonContainer.appendChild(expandAllButton);
        buttonContainer.appendChild(collapseAllButton);
        const parentContainer = this.container.parentElement;
        parentContainer.insertBefore(buttonContainer, this.container);
    }

    // Create header
    const headerRow = document.createElement('tr');
    this.headers.forEach((headerText, index) => {
        const headerCell = document.createElement('th');
        headerCell.textContent = headerText;
        headerCell.style.width = this.columnWidths[index];
        headerRow.appendChild(headerCell);
    });
    header.appendChild(headerRow);

    // Helper functions to calculate date differences and position
    const parseDate = (dateStr) => new Date(dateStr);
    const dateDiffInDays = (a, b) => (parseDate(b) - parseDate(a)) / (1000 * 60 * 60 * 24);

    const totalDays = dateDiffInDays(this.startDate, this.endDate);

    // Create timeline header for days
    const timelineHeaderRow = document.createElement('tr');
    for (let i = 0; i <= totalDays; i++) {
        const dayCell = document.createElement('th');
        dayCell.className = 'day-cell';
        const date = new Date(this.startDate);
        date.setDate(this.startDate.getDate() + i);
        dayCell.textContent = this.formatDate(date);
        timelineHeaderRow.appendChild(dayCell);
    }
    timelineHeader.appendChild(timelineHeaderRow);

    // Function to format date as dd-mm-yy
    const calculatePosition = (start, allocated) => ({
        left: dateDiffInDays(this.startDate, start) * 24,
        width: allocated * 24
    });

    // Group tasks by company
    const tasksByCompany = this.filteredTasks.reduce((acc, task) => {
        acc[task.company] = acc[task.company] || [];
        acc[task.company].push(task);
        return acc;
    }, {});

    // Create rows for each company and their tasks
    Object.keys(tasksByCompany).forEach(company => {
        // Company row
        const companyRow = document.createElement('tr');
        companyRow.className = 'company-row';
        const companyCell = document.createElement('td');
        companyCell.textContent = company;
        companyCell.colSpan = this.headers.length;

        const collapseIcon = document.createElement('span');
        collapseIcon.className = 'collapse-icon';
        collapseIcon.textContent = '▼';
        companyCell.insertBefore(collapseIcon, companyCell.firstChild);

        companyRow.appendChild(companyCell);
        body.appendChild(companyRow);

        const timelineCompanyRow = document.createElement('tr');
        timelineCompanyRow.className = 'company-row';
        const timelineCompanyCell = document.createElement('td');
        timelineCompanyCell.colSpan = totalDays + 1;
        timelineCompanyRow.appendChild(timelineCompanyCell);
        timelineBody.appendChild(timelineCompanyRow);

        // Container for task rows to facilitate collapsing
        const taskRows = [];
        const timelineTaskRows = [];

        // Task rows
        tasksByCompany[company].forEach(task => {
            // Task detail row
            const taskRow = document.createElement('tr');
            taskRow.className = 'task-row';

            const taskRowData = [
                task.priorityArea,
                task.mainTopic,
                task.expert,
                task.allocated,
                task.start,
                task.deadline,
                task.percentCompleted * 100,
                task.status
            ];

            taskRowData.forEach((data, index) => {
                const cell = document.createElement('td');
                cell.textContent = data;
                cell.style.width = this.columnWidths[index];
                taskRow.appendChild(cell);
            });

            taskRows.push(taskRow);

            // Create timeline task row
            const timelineRow = document.createElement('tr');
            timelineRow.className = 'timeline-row';

            // Create the task bar
            const position = calculatePosition(task.start, task.allocated);
            const taskBar = document.createElement('div');
            taskBar.setAttribute('data-bs-toggle', 'popover');
            taskBar.setAttribute('data-bs-trigger', 'hover focus');
            taskBar.setAttribute('data-bs-custom-class', 'custom-popover');
            taskBar.setAttribute('data-bs-title', task.expert);
            taskBar.setAttribute('data-bs-content', task.mainTopic);
            taskBar.setAttribute('data-bs-placement', 'right');
            taskBar.className = 'gantt-task';
            taskBar.style.left = `${position.left}px`;
            taskBar.style.width = `${position.width}px`;
            taskBar.textContent = `${task.percentCompleted * 100}%`;

            // Create the progress bar inside the task bar
            const progressBar = document.createElement('div');
            progressBar.className = 'gantt-task-progress';
            progressBar.style.width = `${task.percentCompleted * 100}px`;
            taskBar.appendChild(progressBar);

            const timelineCell = document.createElement('td');
            timelineCell.colSpan = totalDays + 1;
            timelineCell.style.position = 'relative';
            timelineCell.style.height = '30px';

            timelineCell.appendChild(taskBar);

            timelineRow.appendChild(timelineCell);

            timelineTaskRows.push(timelineRow);
        });

        // Append task rows to the body
        taskRows.forEach(row => body.appendChild(row));
        timelineTaskRows.forEach(row => timelineBody.appendChild(row));

        // Add collapse functionality
        collapseIcon.addEventListener('click', () => {
            const isVisible = taskRows[0].style.display !== 'none';
            taskRows.forEach(row => row.style.display = isVisible ? 'none' : 'table-row');
            timelineTaskRows.forEach(row => row.style.display = isVisible ? 'none' : 'table-row');
            collapseIcon.textContent = isVisible ? '►' : '▼';
        });
    });

    document.querySelectorAll('[data-bs-toggle="popover"]')
        .forEach(popover => {
            new bootstrap.Popover(popover)
        });

    const ganttTasks = document.querySelector('.gantt-tasks');
    const ganttTimeline = document.querySelector('.gantt-timeline');
    const ganttTimelineHeader = document.querySelector('.gantt-timeline-header');

    if (ganttTasks) {
        ganttTasks.addEventListener('scroll', function () {
            ganttTimeline.scrollTop = event.target.scrollTop;
        });
    }
    if (ganttTimeline) {
        ganttTimeline.addEventListener('scroll', function () {
            ganttTasks.scrollTop = event.target.scrollTop;
            ganttTimelineHeader.scrollLeft = event.target.scrollLeft;
        });
    }
    console.log("Gantt chart created!");
}

```

}  
if that can help someone to create a razen Blazor component from it, it would be great

---

<div class="post-metadata">

**Author:** ![Mauro\_Destro](https://forum.radzen.com/user_avatar/forum.radzen.com/mauro_destro/32/20552_2.png) [@Mauro\_Destro](https://forum.radzen.com/u/Mauro_Destro)\
**Post date:** [January 23, 2025, 1:58pm UTC](https://forum.radzen.com/t/gantt-chart/16684/5 "2025-01-23T13:58:39Z")

</div>

It's a good start, please.. the code is not fully readable, can you post it in a way it works?

---

<div class="post-metadata">

**Author:** ![Fady1956](https://forum.radzen.com/letter_avatar_proxy/v4/letter/f/bb73d2/32.png) [@Fady1956](https://forum.radzen.com/u/Fady1956)\
**Post date:** [October 9, 2026, 3:38am UTC](https://forum.radzen.com/t/gantt-chart/16684/6 "2026-10-09T03:38:42Z")

</div>

Hi,  
further to your comment, I need to note that not only you changed direction and created the Gantt component, the component you created is mature enough and really useful. Thank you.

---

<div class="post-metadata">

**Author:** ![korchev](https://forum.radzen.com/user_avatar/forum.radzen.com/korchev/32/4_2.png) [@korchev](https://forum.radzen.com/u/korchev)\
**Post date:** [October 12, 2026, 12:34pm UTC](https://forum.radzen.com/t/gantt-chart/16684/7 "2026-10-12T12:34:03Z")

</div>

Thanks @Fady1956, glad it's useful!

For anyone who finds this thread: Radzen Blazor has included a native Gantt component since v10, free and open source (MIT) as part of the Radzen.Blazor package.

- Hierarchical task list with a timeline, day, week, month and year zoom levels, plus month or quarter cells for multi-year projects
- Drag to move and resize tasks, milestones, inline and in-cell editing, filtering
- All four dependency types, critical path and baselines
- Resource view that groups tasks by resource (new in v12)

Live demos with source: [Blazor Gantt Component | Free Radzen Blazor](https://blazor.radzen.com/gantt)

---

<div class="post-metadata">

**Author:** ![Fady1956](https://forum.radzen.com/letter_avatar_proxy/v4/letter/f/bb73d2/32.png) [@Fady1956](https://forum.radzen.com/u/Fady1956)\
**Post date:** [October 13, 2026, 4:07pm UTC](https://forum.radzen.com/t/gantt-chart/16684/8 "2026-10-13T16:07:07Z")

</div>

and what a good and useful component!
