Gantt Chart

ULTIMATE

The Gantt extension adds a timeline lane to every row of a worksheet. The task name, start date, end date, progress, colour and predecessors are read from the columns you choose, and the bars are drawn next to the data, in the same table, so rows and bars always line up, through scrolling, sorting and filtering.

Editing works in both directions. Change a date in a cell and the bar follows. Drag a bar to move it, drag its edge to resize it, or drag the dot at its end onto another task to create a dependency, and the extension writes the new values back to the cells through the normal spreadsheet pipeline, so undo, redo, events and the collaboration server all see ordinary cell changes.

Key Features

  • Bars aligned with the grid rows, inside the same table
  • Drag to move, drag the edges to resize, with snapping in days
  • Finish to start dependencies drawn as arrows, created and removed with the mouse
  • Progress fill and a colour per task, read from the cells
  • Week and day scale, today marker, resizable and zoomable timeline
  • Every edit goes through setValue: undo, redo and events work as usual
  • Self-contained build, with no runtime dependencies

Documentation

Settings

The extension is configured once, before it is registered with setExtensions.

Property Description
columns: object Which column holds each task field. See the columns table below. Default: { label: 0, start: 1, end: 2, progress: 3, color: 4 }
width: number Width of the timeline column, in pixels. Default: 420
daywidth: number Fixed width of one day, in pixels. The timeline then shows width / daywidth days. Use 0 to fit all tasks. Default: 16
editable: boolean Drag bars to move and resize them, and draw dependencies. Default: true
readonly: boolean View only, regardless of editable. Use it to mirror a read-only worksheet. Default: false
snap: number Drag snapping, in days. Default: 1
resizable: boolean A grip on the right border of the timeline to change its width. Default: true
zoom: boolean Shift + mouse wheel over the timeline changes the day width. Default: true

Columns

Each entry of columns is a zero-based column index.

Field Description
label The task name, shown next to the bar
start The start date. An ISO string such as 2026-10-05, or a calendar column
end The end date. A task without an end date is one day long
progress? Completion from 0 to 100, drawn as a lighter fill inside the bar
color? A CSS colour for the bar, for example from a color column
predecessors? Comma-separated row numbers of the tasks this one depends on, counted from 1. Enables the dependency arrows

Rows without a valid start date have an empty lane. Dates stored as spreadsheet serial numbers, which is how typed dates are kept, are read as well.

Interaction

Gesture Effect
Drag a bar Moves the task. The start and end cells are updated on drop
Drag the left or right edge of a bar Changes the start or the end date
Drag the dot at the end of a bar to another row Adds that row to the predecessors of the target task
Click an arrow Removes that dependency
Drag the timeline header Pans the visible dates
Shift + mouse wheel over the timeline Zooms the day width, keeping the date under the cursor in place
Drag the right border of the timeline Resizes the timeline column, keeping the day width

Every change made on the chart is a cell change, so onchange, onafterchanges and the history behave exactly as if the user had typed the new value.

Installation

Choose one of the following options:

Using NPM

$ npm install @jspreadsheet/gantt

Using a CDN

<script src="https://cdn.jsdelivr.net/npm/@jspreadsheet/gantt/dist/index.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jspreadsheet/gantt/dist/style.css" type="text/css" />

The file is self-contained and has no runtime dependencies. It runs next to any other Jspreadsheet extension.

Code examples

A project plan with dependencies

Each row is a task. The last column lists the rows it depends on, which draws the arrows.

<html>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspreadsheet@13/dist/index.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jspreadsheet@13/dist/jspreadsheet.min.css" type="text/css" />
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />

<script src="https://cdn.jsdelivr.net/npm/@jspreadsheet/gantt/dist/index.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jspreadsheet/gantt/dist/style.css" type="text/css" />

<div id="spreadsheet"></div>

<script>
// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// This license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate an extended demo license.
jspreadsheet.setLicense('NGIzZWQwZTBiMjcwZmMwMDZhZjM1NGUyNDcxYWJlMzZlMmExNmRhMGUyY2JlMDlkYWYyMDgwYzUyZjM0YTNjZjM4MmFiYjFjMjQ5NTRiMDA3YjU0YWZmZTVkYzI0NTA4MjgxZTI4Nzg5ODgyZTQzYzcyYzNiYmUzOTY3NjdkMDMsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpreE1EYzVNelk0TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Which columns feed the chart
gantt({
    columns: { label: 0, start: 1, end: 2, progress: 3, color: 4, predecessors: 5 },
    width: 480,
    daywidth: 16,
});

// Spreadsheet plugin
jspreadsheet.setExtensions({ gantt });

// Create a spreadsheet
jspreadsheet(document.getElementById('spreadsheet'), {
    tableOverflow: true,
    tableWidth: '100%',
    tableHeight: 400,
    worksheets: [{
        worksheetName: 'Project',
        columns: [
            { title: 'Task', width: 160 },
            { title: 'Start', type: 'calendar', width: 110 },
            { title: 'End', type: 'calendar', width: 110 },
            { title: '%', type: 'number', width: 50 },
            { title: 'Color', type: 'color', render: 'square', width: 80 },
            { title: 'After', width: 70 },
        ],
        data: [
            ['Discovery', '2026-10-05', '2026-10-09', 100, '#2563eb', ''],
            ['Requirements', '2026-10-12', '2026-10-16', 100, '#2563eb', '1'],
            ['Design', '2026-10-15', '2026-10-23', 60, '#7c3aed', '2'],
            ['API', '2026-10-26', '2026-11-06', 30, '#0d9488', '3'],
            ['Front end', '2026-10-28', '2026-11-11', 20, '#0d9488', '3'],
            ['Integration', '2026-11-12', '2026-11-18', 0, '#d97706', '4,5'],
            ['QA', '2026-11-19', '2026-11-26', 0, '#d97706', '6'],
            ['Launch', '2026-11-30', '2026-11-30', 0, '#dc2626', '7'],
        ],
    }],
});
</script>
</html>
import React, { useRef } from "react";
import { Spreadsheet, Worksheet } from "@jspreadsheet/react";
import jspreadsheet from "jspreadsheet";
import gantt from "@jspreadsheet/gantt";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
import "@jspreadsheet/gantt/dist/style.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// This license is valid for one day, after which the spreadsheet will become read-only.
// For a longer trial period, you can create a free account and generate a demo license with a longer validity period.
jspreadsheet.setLicense('NGIzZWQwZTBiMjcwZmMwMDZhZjM1NGUyNDcxYWJlMzZlMmExNmRhMGUyY2JlMDlkYWYyMDgwYzUyZjM0YTNjZjM4MmFiYjFjMjQ5NTRiMDA3YjU0YWZmZTVkYzI0NTA4MjgxZTI4Nzg5ODgyZTQzYzcyYzNiYmUzOTY3NjdkMDMsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpreE1EYzVNelk0TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Which columns feed the chart
gantt({
    columns: { label: 0, start: 1, end: 2, progress: 3, color: 4, predecessors: 5 },
    width: 480,
});

// JSS data grid plugin
jspreadsheet.setExtensions({ gantt });

const columns = [
    { title: 'Task', width: 160 },
    { title: 'Start', type: 'calendar', width: 110 },
    { title: 'End', type: 'calendar', width: 110 },
    { title: '%', type: 'number', width: 50 },
    { title: 'Color', type: 'color', render: 'square', width: 80 },
    { title: 'After', width: 70 },
];

const data = [
    ['Discovery', '2026-10-05', '2026-10-09', 100, '#2563eb', ''],
    ['Requirements', '2026-10-12', '2026-10-16', 100, '#2563eb', '1'],
    ['Design', '2026-10-15', '2026-10-23', 60, '#7c3aed', '2'],
    ['API', '2026-10-26', '2026-11-06', 30, '#0d9488', '3'],
    ['Front end', '2026-10-28', '2026-11-11', 20, '#0d9488', '3'],
    ['Integration', '2026-11-12', '2026-11-18', 0, '#d97706', '4,5'],
];

export default function App() {
    // Spreadsheet worksheet array
    const spreadsheet = useRef();

    // Rendering component
    return (
        <Spreadsheet ref={spreadsheet} tableOverflow tableWidth="100%" tableHeight={400}>
            <Worksheet worksheetName="Project" columns={columns} data={data} />
        </Spreadsheet>
    );
}
<template>
    <Spreadsheet ref="spreadsheet" :license="license" :extensions="extensions" :tableOverflow="true" tableWidth="100%" :tableHeight="400">
        <Worksheet worksheetName="Project" :columns="columns" :data="data" />
    </Spreadsheet>
</template>

<script>
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/vue";
import gantt from "@jspreadsheet/gantt";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";
import "@jspreadsheet/gantt/dist/style.css";

// Define the data grid license
const license = 'NGIzZWQwZTBiMjcwZmMwMDZhZjM1NGUyNDcxYWJlMzZlMmExNmRhMGUyY2JlMDlkYWYyMDgwYzUyZjM0YTNjZjM4MmFiYjFjMjQ5NTRiMDA3YjU0YWZmZTVkYzI0NTA4MjgxZTI4Nzg5ODgyZTQzYzcyYzNiYmUzOTY3NjdkMDMsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpreE1EYzVNelk0TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=';

// Which columns feed the chart
gantt({
    columns: { label: 0, start: 1, end: 2, progress: 3, color: 4, predecessors: 5 },
    width: 480,
});

// Define the data grid extensions
const extensions = { gantt };

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    data() {
        return {
            license,
            extensions,
            columns: [
                { title: 'Task', width: 160 },
                { title: 'Start', type: 'calendar', width: 110 },
                { title: 'End', type: 'calendar', width: 110 },
                { title: '%', type: 'number', width: 50 },
                { title: 'Color', type: 'color', render: 'square', width: 80 },
                { title: 'After', width: 70 },
            ],
            data: [
                ['Discovery', '2026-10-05', '2026-10-09', 100, '#2563eb', ''],
                ['Requirements', '2026-10-12', '2026-10-16', 100, '#2563eb', '1'],
                ['Design', '2026-10-15', '2026-10-23', 60, '#7c3aed', '2'],
                ['API', '2026-10-26', '2026-11-06', 30, '#0d9488', '3'],
                ['Front end', '2026-10-28', '2026-11-11', 20, '#0d9488', '3'],
                ['Integration', '2026-11-12', '2026-11-18', 0, '#d97706', '4,5'],
            ],
        };
    }
}
</script>
import { Component, ViewChild, ElementRef } from "@angular/core";
import jspreadsheet from "jspreadsheet";
import gantt from "@jspreadsheet/gantt";
import "@jspreadsheet/gantt/dist/style.css";

// You can use the following license for quick testing on localhost, StackBlitz, or CodeSandbox.
// This license is valid for one day, after which the spreadsheet will become read-only.
jspreadsheet.setLicense('NGIzZWQwZTBiMjcwZmMwMDZhZjM1NGUyNDcxYWJlMzZlMmExNmRhMGUyY2JlMDlkYWYyMDgwYzUyZjM0YTNjZjM4MmFiYjFjMjQ5NTRiMDA3YjU0YWZmZTVkYzI0NTA4MjgxZTI4Nzg5ODgyZTQzYzcyYzNiYmUzOTY3NjdkMDMsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpreE1EYzVNelk0TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2liRzlqWVd4b2IzTjBJbDBzSW5Cc1lXNGlPaUl6TkNJc0luTmpiM0JsSWpwYkluWTNJaXdpZGpnaUxDSjJPU0lzSW5ZeE1DSXNJbll4TVNJc0luWXhNaUlzSW5ZeE15SXNJbU5vWVhKMGN5SXNJbVp2Y20xeklpd2labTl5YlhWc1lTSXNJbkJoY25ObGNpSXNJbkpsYm1SbGNpSXNJbU52YlcxbGJuUnpJaXdpYVcxd2IzSjBaWElpTENKaVlYSWlMQ0oyWVd4cFpHRjBhVzl1Y3lJc0luTmxZWEpqYUNJc0luQnlhVzUwSWl3aWMyaGxaWFJ6SWl3aVkyeHBaVzUwSWl3aWMyVnlkbVZ5SWl3aWMyaGhjR1Z6SWl3aVptOXliV0YwSWl3aWNHbDJiM1FpWFN3aVpHVnRieUk2ZEhKMVpYMD0=');

// Which columns feed the chart
gantt({
    columns: { label: 0, start: 1, end: 2, progress: 3, color: 4, predecessors: 5 },
    width: 480,
});

// Extend
jspreadsheet.setExtensions({ gantt });

@Component({
    standalone: true,
    selector: "app-root",
    template: `<div #spreadsheet></div>`
})
export class AppComponent {
    @ViewChild("spreadsheet") spreadsheet: ElementRef;
    // Worksheets
    worksheets: jspreadsheet.worksheetInstance[];
    // Create a new data grid
    ngAfterViewInit() {
        // Create a spreadsheet
        this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
            tableOverflow: true,
            tableWidth: '100%',
            tableHeight: 400,
            worksheets: [{
                worksheetName: 'Project',
                columns: [
                    { title: 'Task', width: 160 },
                    { title: 'Start', type: 'calendar', width: 110 },
                    { title: 'End', type: 'calendar', width: 110 },
                    { title: '%', type: 'number', width: 50 },
                    { title: 'Color', type: 'color', render: 'square', width: 80 },
                    { title: 'After', width: 70 },
                ],
                data: [
                    ['Discovery', '2026-10-05', '2026-10-09', 100, '#2563eb', ''],
                    ['Requirements', '2026-10-12', '2026-10-16', 100, '#2563eb', '1'],
                    ['Design', '2026-10-15', '2026-10-23', 60, '#7c3aed', '2'],
                    ['API', '2026-10-26', '2026-11-06', 30, '#0d9488', '3'],
                    ['Front end', '2026-10-28', '2026-11-11', 20, '#0d9488', '3'],
                    ['Integration', '2026-11-12', '2026-11-18', 0, '#d97706', '4,5'],
                ],
            }],
        });
    }
}

A read-only timeline

Use readonly to show the chart without editing, for example on a shared view, while the cells stay editable or not according to the worksheet.

gantt({
    columns: { label: 0, start: 1, end: 2 },
    readonly: true,
    daywidth: 0, // fit every task in the timeline
});

Examples

Integration with React

React spreadsheet working example.