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

FieldTypeDescription
taskIdstringID of the task whose progress changed
oldProgressnumberPrevious completion value (0–100)
newProgressnumberNew completion value (0–100)
timestampnumberDate.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>