Creating a Basic Gantt Chart
This page walks through building a Gantt chart from scratch using ApexGantt: starting with a flat list of tasks, adding a hierarchy, then adding dependencies between tasks.
If you have not installed ApexGantt yet, see Installation & Usage.
A flat task list
The minimum chart requires an array of tasks, each with an id, name, startTime, and endTime.
<div id="gantt" style="height: 300px;"></div>
import ApexGantt from 'apexgantt'
ApexGantt.setLicense('YOUR-LICENSE-KEY')
const gantt = new ApexGantt(document.getElementById('gantt'), {
inputDateFormat: 'YYYY-MM-DD', // the dates below are ISO; the default is 'MM-DD-YYYY'
series: [
{ id: '1', name: 'Discovery', startTime: '2024-04-01', endTime: '2024-04-07' },
{ id: '2', name: 'Design', startTime: '2024-04-08', endTime: '2024-04-20' },
{ id: '3', name: 'Development', startTime: '2024-04-21', endTime: '2024-05-10' },
{ id: '4', name: 'Testing', startTime: '2024-05-06', endTime: '2024-05-15' },
{ id: '5', name: 'Launch', startTime: '2024-05-16', endTime: '2024-05-17' },
],
})
gantt.render()
ApexGantt draws a bar for each task spanning its start and end dates, with a task list panel on the left showing the task names.
The task list is sorted by start date by default, not by array order. Pass sortBy: [] to keep the order of series. See Sorting and Filtering.
The chart is 500px tall unless you set height. Since 3.20.0, when height is not set and the container has a height of its own (the 300px above), the chart fills the container instead and follows it when it grows or shrinks. On 3.19 and earlier, set height: 300 to match. Since 3.20.0 a chart that fills its container with more than 50 rows (rows are then virtualised) draws rows down to the bottom of the container on first paint; before, only about 30 rows were drawn and the rest of the panel stayed blank until the user scrolled.
Since 3.20.0 a chart with no height whose container is sized by its content no longer resets the page scroll when it re-renders in full (collapsing or expanding a row, update(), sorting, filtering, adding or deleting a task).
Adding progress
Set progress (0-100) on any task to render a filled segment inside the bar.
series: [
{ id: '1', name: 'Discovery', startTime: '2024-04-01', endTime: '2024-04-07', progress: 100 },
{ id: '2', name: 'Design', startTime: '2024-04-08', endTime: '2024-04-20', progress: 75 },
{ id: '3', name: 'Development', startTime: '2024-04-21', endTime: '2024-05-10', progress: 20 },
{ id: '4', name: 'Testing', startTime: '2024-05-06', endTime: '2024-05-15', progress: 0 },
]
Adding a hierarchy
Link tasks to a parent by setting parentId to the parent's id. The parent row becomes a summary bar that auto-spans the earliest start to the latest end of its children.
series: [
// Parent (summary) rows
{ id: 'p1', name: 'Phase 1: Research', startTime: '2024-04-01', endTime: '2024-04-14', progress: 100 },
// Children
{ id: 'c1', name: 'Stakeholder interviews', startTime: '2024-04-01', endTime: '2024-04-05', parentId: 'p1', progress: 100 },
{ id: 'c2', name: 'Competitive analysis', startTime: '2024-04-03', endTime: '2024-04-10', parentId: 'p1', progress: 100 },
{ id: 'c3', name: 'Requirements doc', startTime: '2024-04-08', endTime: '2024-04-14', parentId: 'p1', progress: 100 },
// Second phase
{ id: 'p2', name: 'Phase 2: Design', startTime: '2024-04-15', endTime: '2024-04-30', progress: 50 },
{ id: 'c4', name: 'Wireframes', startTime: '2024-04-15', endTime: '2024-04-22', parentId: 'p2', progress: 100 },
{ id: 'c5', name: 'Visual design', startTime: '2024-04-20', endTime: '2024-04-30', parentId: 'p2', progress: 20 },
]
The hierarchy can be nested to any depth. A task with children is treated as a summary row and rendered differently from leaf tasks.
Since 3.20.0 a parent row may leave out its dates (both, or only endTime): its summary bar spans its children. A task with no startTime and no children still throws (Task "<id>" has no startTime and no children. Provide dates or add child tasks.), and a call that throws leaves the chart's previous data in place.
parentId is compared by value, so parentId: '1' finds a task whose id is the number 1 (dependency ids work the same way). A parentId that matches no task, or that makes a cycle (a task naming itself, or a under b under a), logs a warning and the task is shown at the root. For a cycle the warning reads [ApexGantt] Task "<id>" has parentId "<p>", which makes a cycle of parents. It is shown at the root.; before 3.20.0 a cycle overflowed the stack.
Adding dependencies
Set dependency on a task to the id of the task it depends on. ApexGantt draws an arrow between the tasks.
series: [
{ id: '1', name: 'Discovery', startTime: '2024-04-01', endTime: '2024-04-07', progress: 100 },
{ id: '2', name: 'Design', startTime: '2024-04-08', endTime: '2024-04-20', progress: 75, dependency: '1' },
{ id: '3', name: 'Development', startTime: '2024-04-21', endTime: '2024-05-10', progress: 20, dependency: '2' },
{ id: '4', name: 'Testing', startTime: '2024-05-06', endTime: '2024-05-15', progress: 0, dependency: '3' },
]
A task takes one dependency in its data. When it has more than one predecessor, add the others after render with addDependency. Here Testing also waits for Design:
gantt.render()
gantt.addDependency('2', '4')
An array in dependency is not read and draws no arrow. See Dependencies for dependency types and lag.
Dependencies are drawn and analysed (critical path, the Predecessors and Successors columns) but never move tasks. ApexGantt does not auto-schedule: when Design slips, Development keeps its dates until you change them.
Customizing the date format
Dates are parsed with inputDateFormat, using dayjs tokens. The default is 'MM-DD-YYYY', which is why the examples on this page set 'YYYY-MM-DD' for their ISO dates. For day-first data:
const gantt = new ApexGantt(el, {
inputDateFormat: 'DD/MM/YYYY',
series: [...],
})
ISO dates under the default format still draw, because parsing falls back to a lenient read, but edited or dragged dates are written back as MM-DD-YYYY. On 3.19 and earlier the edit dialog also showed empty date fields for them; since 3.20.0 the dialog reads ISO and other formats, and an inline date editor left unchanged no longer rewrites the stored value. Set the format to match your data.
For sub-day scheduling, add time tokens:
inputDateFormat: 'YYYY-MM-DD HH:mm'
With time tokens in the format and snapUnit set to 'hour' or 'minute', endTime is treated as an exclusive timestamp (the task ends at that exact moment, not at the end of that day).
Controlling what is visible
On first render ApexGantt fits the full data range into the timeline. Zoom with pixelsPerDay (pixels per day of timeline), and bring a task into view with scrollToTask. Since 3.20.0 scrollToTask first expands any collapsed parents of the task:
const gantt = new ApexGantt(el, {
series: [...],
pixelsPerDay: 40,
})
gantt.render()
gantt.scrollToTask('4')
Next steps
- Options reference for all configuration options including theming, columns, and toolbars
- Methods reference for programmatic task manipulation, undo/redo, and zoom control
- Framework guides: React, Vue, Angular