Progress Tracking
ApexGantt renders a filled portion inside each task bar to represent completion. The progress field on a task accepts a value from 0 to 100. This page covers how to display, edit, and listen for progress changes.
Setting initial progress
Pass progress on each TaskInput. It defaults to 0 when omitted:
const tasks = [
{
id: 'task-1',
name: 'Design',
startTime: '2024-03-01',
endTime: '2024-03-15',
progress: 100, // complete
},
{
id: 'task-2',
name: 'Development',
startTime: '2024-03-16',
endTime: '2024-04-15',
progress: 45, // in progress
},
{
id: 'task-3',
name: 'QA',
startTime: '2024-04-16',
endTime: '2024-04-30',
progress: 0, // not started
},
]
Interactive progress drag
enableProgressDrag: true adds a drag handle at the bottom edge of each task bar. The user drags it left or right to set completion. The value snaps to whole percents on commit.
const gantt = new ApexGantt(document.getElementById('chart'), {
series: tasks,
enableProgressDrag: true,
})
taskProgressChanged event
When the user drags the progress handle, taskProgressChanged fires after the value commits:
gantt.el.addEventListener('taskProgressChanged', (e) => {
const { taskId, oldProgress, newProgress } = e.detail
console.log(`${taskId}: ${oldProgress}% → ${newProgress}%`)
// save to backend
})
Event detail payload
| Field | Type | Description |
|---|---|---|
taskId | string | ID of the task whose progress changed |
oldProgress | number | Previous completion value (0–100) |
newProgress | number | New completion value (0–100) |
timestamp | number | Date.now() at the time of the event |
Progress drag is recorded in the undo history automatically — undo() restores the previous value.
Programmatic progress update
Call updateTask() to set progress from code — for example, after polling a backend or receiving a WebSocket event:
// mark a single task done
gantt.updateTask('task-2', { progress: 100 })
// bulk update from API response
apiTasks.forEach(({ id, percentComplete }) => {
gantt.updateTask(id, { progress: percentComplete })
})
updateTask() accepts any partial TaskInput fields and re-renders without a full chart rebuild.
Showing progress in the task list
Add a progress or progressRing column to columnConfig to display the value alongside the task name:
const gantt = new ApexGantt(el, {
series: tasks,
columnConfig: [
{ key: 'name', title: 'Task', minWidth: '180px', flexGrow: 3 },
{ key: 'startTime', title: 'Start', minWidth: '100px', flexGrow: 1 },
{ key: 'endTime', title: 'End', minWidth: '100px', flexGrow: 1 },
{ key: 'progress', title: '%', minWidth: '60px', flexGrow: 0 },
{ key: 'progressRing',title: '', minWidth: '36px', flexGrow: 0 },
],
})
progress renders the numeric value as text (e.g. 45%). progressRing renders a circular indicator.
Custom progress column
Use a render function to build a bar-style progress indicator in the task list:
{
key: 'progressBar',
title: 'Progress',
minWidth: '120px',
flexGrow: 2,
render: (ctx) => {
const pct = ctx.task.progress
const color = pct === 100 ? '#10B981' : pct > 0 ? '#3B82F6' : '#E5E7EB'
return `
<div style="display:flex;align-items:center;gap:8px;width:100%">
<div style="flex:1;background:#F1F5F9;border-radius:4px;height:6px;overflow:hidden">
<div style="width:${pct}%;background:${color};height:100%;border-radius:4px;transition:width .2s"></div>
</div>
<span style="font-size:11px;color:#64748B;white-space:nowrap">${pct}%</span>
</div>
`
},
accessor: (task) => task.progress,
},
Validating progress changes
Use beforeTaskUpdate to intercept progress drag before it commits:
const gantt = new ApexGantt(el, {
series: tasks,
enableProgressDrag: true,
beforeTaskUpdate: (taskId, changes, currentTask) => {
// prevent reducing progress (no going backwards)
if (changes.progress !== undefined && changes.progress < currentTask.progress) {
return false
}
return true
},
})
Real-time progress from a server
Poll or subscribe and push updates to the chart without triggering a full re-render:
const gantt = new ApexGantt(el, { series: tasks })
// WebSocket example
const ws = new WebSocket('wss://api.example.com/gantt-events')
ws.addEventListener('message', (e) => {
const { taskId, progress } = JSON.parse(e.data)
gantt.updateTask(taskId, { progress })
})
React example
import { useRef, useEffect } from 'react'
import { ApexGanttChart } from 'react-apexgantt'
import type { ApexGanttHandle, GanttUserOptions } from 'react-apexgantt'
const options: Omit<GanttUserOptions, 'series'> = {
enableProgressDrag: true,
columnConfig: [
{ key: 'name', title: 'Task', minWidth: '180px', flexGrow: 3 },
{ key: 'progressRing', title: '', minWidth: '36px', flexGrow: 0 },
{ key: 'progress', title: '%', minWidth: '60px', flexGrow: 0 },
],
}
export default function ProgressGantt() {
const ganttRef = useRef<ApexGanttHandle>(null)
useEffect(() => {
const el = ganttRef.current?.getInstance()?.el
if (!el) return
const handler = (e: Event) => {
const { taskId, newProgress } = (e as CustomEvent).detail
// persist to backend
fetch(`/api/tasks/${taskId}`, {
method: 'PATCH',
body: JSON.stringify({ progress: newProgress }),
headers: { 'Content-Type': 'application/json' },
})
}
el.addEventListener('taskProgressChanged', handler)
return () => el.removeEventListener('taskProgressChanged', handler)
}, [])
return (
<ApexGanttChart
ref={ganttRef}
tasks={tasks}
options={options}
height="500px"
/>
)
}
Vue example
<template>
<ApexGanttChart
:tasks="tasks"
:options="ganttOptions"
height="500px"
@task-progress-changed="onProgressChanged"
/>
</template>
<script setup lang="ts">
import type { GanttUserOptions } from 'vue-apexgantt'
const ganttOptions: Partial<GanttUserOptions> = {
enableProgressDrag: true,
columnConfig: [
{ key: 'name', title: 'Task', minWidth: '180px', flexGrow: 3 },
{ key: 'progressRing', title: '', minWidth: '36px', flexGrow: 0 },
{ key: 'progress', title: '%', minWidth: '60px', flexGrow: 0 },
],
}
const onProgressChanged = (detail: { taskId: string; newProgress: number }) => {
console.log(`${detail.taskId}: now ${detail.newProgress}%`)
// persist to backend
}
</script>