Timeline Views and Zoom

ApexGantt renders tasks on a scrollable horizontal timeline where the zoom level determines how many pixels represent one calendar day. That single number controls how much time fits in the visible area: a high value zooms in to show a few days in detail, a low value zooms out to show months or years at a glance.

View modes

ApexGantt ships five named view modes. Each mode is a shorthand for a specific pixelsPerDay value and drives which calendar tiers appear in the timeline header.

ModeviewMode valueApproximate time on screenTypical use
Day'day'1-2 weeksDetailed scheduling, hour-level work
Week'week'1-3 monthsSprint planning, short projects
Month'month'3-6 monthsMost projects (default)
Quarter'quarter'1-2 yearsRoadmaps, long-term planning
Year'year'Multi-yearExecutive overviews

The ViewMode type is a string union: 'day' | 'week' | 'month' | 'quarter' | 'year'.

Setting viewMode at construction

Pass viewMode in the options object. If omitted, it defaults to 'month'.

import ApexGantt from 'apexgantt'

ApexGantt.setLicense('YOUR-LICENSE-KEY')

const gantt = new ApexGantt(document.getElementById('gantt'), {
  series: [
    { id: '1', name: 'Discovery',   startTime: '2024-04-01', endTime: '2024-04-14' },
    { id: '2', name: 'Design',      startTime: '2024-04-15', endTime: '2024-04-30' },
    { id: '3', name: 'Development', startTime: '2024-05-01', endTime: '2024-06-15' },
    { id: '4', name: 'Testing',     startTime: '2024-06-10', endTime: '2024-06-30' },
  ],
  viewMode: 'week',
})

gantt.render()

Switching viewMode at runtime

Call update() with the new viewMode. The timeline re-renders immediately without discarding task data.

// Switch to a quarterly overview
gantt.update({ viewMode: 'quarter' })

// Switch back to week-level detail
gantt.update({ viewMode: 'week' })

update() accepts a partial options object, so you can change only viewMode while leaving all other configuration untouched.

Auto-fit zoom

When you omit both viewMode and pixelsPerDay, ApexGantt calculates a pixelsPerDay value that fits the full span of your task data into the available container width on first render. This is the default behavior. It is useful when you do not know the date range of the data ahead of time and want the chart to always start fully visible without horizontal scrolling.

Once the user scrolls or a programmatic zoom is applied, the auto-fit value is no longer re-calculated.

Fine-grained zoom with pixelsPerDay

viewMode is a convenience. Under the hood, ApexGantt works entirely with pixelsPerDay. Setting it directly gives you continuous control: any value between roughly 0.25 and 1280 is accepted.

When pixelsPerDay is set explicitly, it overrides the viewMode preset. The timeline header automatically selects the appropriate calendar tiers (year, quarter, month, week, day, halfday, hour, minute) based on the current value.

Calibrated reference values that correspond to each named view:

Named viewApproximate pixelsPerDay
'year'0.5
'quarter'1.6
'month'4.9
'week'25.7
'day'80
// Start at a custom density between 'week' and 'day'
const gantt = new ApexGantt(document.getElementById('gantt'), {
  series: tasks,
  pixelsPerDay: 40,
})

gantt.render()

You can also update it at runtime:

gantt.update({ pixelsPerDay: 25.7 })  // equivalent to week view

Programmatic zoom buttons

zoomIn() and zoomOut() step through the named zoom levels. Each call moves one level in the direction indicated. The underlying pixelsPerDay is updated and the header tiers adjust automatically.

  • zoomIn() moves toward finer detail: Year → Quarter → Month → Week → Day. Calling zoomIn() when already at Day has no effect.
  • zoomOut() moves toward coarser overview: Day → Week → Month → Quarter → Year. Calling zoomOut() when already at Year has no effect.

A minimal toolbar using plain buttons:

<div id="gantt" style="height: 400px;"></div>
<div class="zoom-controls">
  <button id="zoom-in">Zoom in</button>
  <button id="zoom-out">Zoom out</button>
</div>
import ApexGantt from 'apexgantt'

ApexGantt.setLicense('YOUR-LICENSE-KEY')

const gantt = new ApexGantt(document.getElementById('gantt'), {
  series: [
    { id: '1', name: 'Planning',    startTime: '2024-01-01', endTime: '2024-03-31' },
    { id: '2', name: 'Execution',   startTime: '2024-04-01', endTime: '2024-09-30' },
    { id: '3', name: 'Review',      startTime: '2024-10-01', endTime: '2024-12-31' },
  ],
  viewMode: 'month',
})

gantt.render()

document.querySelector('#zoom-in').addEventListener('click', () => gantt.zoomIn())
document.querySelector('#zoom-out').addEventListener('click', () => gantt.zoomOut())

The built-in toolbar also includes zoom controls automatically. If you prefer to rely on those instead of custom buttons, set toolbarItems to include the zoom items or leave the toolbar at its default configuration.

React

In React, bind viewMode to component state. The react-apexgantt wrapper diffs props on every render and calls update() internally only when a prop changes, so you do not need to manage the chart instance manually.

import { useState } from 'react'
import { ApexGanttChart } from 'react-apexgantt'
import type { ViewMode } from 'react-apexgantt'

const tasks = [
  { id: '1', name: 'Planning',  startTime: '2024-01-01', endTime: '2024-03-31' },
  { id: '2', name: 'Execution', startTime: '2024-04-01', endTime: '2024-09-30' },
  { id: '3', name: 'Review',    startTime: '2024-10-01', endTime: '2024-12-31' },
]

export default function GanttWithViewSelector() {
  const [viewMode, setViewMode] = useState<ViewMode>('month')

  return (
    <div>
      <div style={{ marginBottom: 12, display: 'flex', gap: 8 }}>
        {(['day', 'week', 'month', 'quarter', 'year'] as ViewMode[]).map((m) => (
          <button
            key={m}
            style={{ fontWeight: viewMode === m ? 'bold' : 'normal' }}
            onClick={() => setViewMode(m)}
          >
            {m.charAt(0).toUpperCase() + m.slice(1)}
          </button>
        ))}
      </div>
      <ApexGanttChart tasks={tasks} viewMode={viewMode} height="400px" />
    </div>
  )
}

To call zoomIn() and zoomOut() from buttons in React, use the imperative ref API. See React ApexGantt for the full ApexGanttHandle reference.

import { useRef } from 'react'
import { ApexGanttChart } from 'react-apexgantt'
import type { ApexGanttHandle } from 'react-apexgantt'

export default function GanttWithZoom() {
  const ganttRef = useRef<ApexGanttHandle>(null)

  return (
    <div>
      <div style={{ marginBottom: 12, display: 'flex', gap: 8 }}>
        <button onClick={() => ganttRef.current?.zoomIn()}>Zoom in</button>
        <button onClick={() => ganttRef.current?.zoomOut()}>Zoom out</button>
      </div>
      <ApexGanttChart ref={ganttRef} tasks={tasks} height="400px" />
    </div>
  )
}

Vue

In Vue, bind view-mode to a ref. The vue-apexgantt wrapper handles the update() call when the prop changes.

<template>
  <div>
    <div style="margin-bottom: 12px; display: flex; gap: 8px;">
      <button
        v-for="m in viewModes"
        :key="m"
        :style="{ fontWeight: viewMode === m ? 'bold' : 'normal' }"
        @click="viewMode = m"
      >
        {{ m.charAt(0).toUpperCase() + m.slice(1) }}
      </button>
    </div>
    <ApexGanttChart :tasks="tasks" :view-mode="viewMode" height="400px" />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { ApexGanttChart } from 'vue-apexgantt'
import type { ViewMode } from 'vue-apexgantt'

const viewModes: ViewMode[] = ['day', 'week', 'month', 'quarter', 'year']
const viewMode = ref<ViewMode>('month')

const tasks = [
  { id: '1', name: 'Planning',  startTime: '2024-01-01', endTime: '2024-03-31' },
  { id: '2', name: 'Execution', startTime: '2024-04-01', endTime: '2024-09-30' },
  { id: '3', name: 'Review',    startTime: '2024-10-01', endTime: '2024-12-31' },
]
</script>

To call zoomIn() and zoomOut() from Vue, use a template ref. See Vue ApexGantt for the full template ref method list.

Crosshair

The crosshair is a vertical line that tracks the cursor across the timeline. It shows a date label at the top of the timeline as the user moves the mouse, making it easy to read off exact dates on dense charts.

Enable it with enableCrosshair: true:

const gantt = new ApexGantt(document.getElementById('gantt'), {
  series: tasks,
  enableCrosshair: true,
})

gantt.render()

Crosshair color

Change the line color with crosshairColor. Any CSS color value is accepted.

const gantt = new ApexGantt(document.getElementById('gantt'), {
  series: tasks,
  enableCrosshair: true,
  crosshairColor: '#F59E0B',
})

gantt.render()

Custom crosshair label

The crosshairLabelFormat callback lets you control the text shown in the date label. It receives the Date under the cursor and a tier string that describes which calendar resolution is currently active at the cursor position.

const gantt = new ApexGantt(document.getElementById('gantt'), {
  series: tasks,
  enableCrosshair: true,
  crosshairLabelFormat: (date, tier) => {
    // tier: 'minute' | 'hour' | 'halfday' | 'day' | 'week' | 'month' | 'quarter' | 'year'
    if (tier === 'day' || tier === 'halfday') {
      return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })
    }
    if (tier === 'hour' || tier === 'minute') {
      return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })
    }
    return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' })
  },
})

gantt.render()

The tier value matches the current header resolution, so the label can stay contextually relevant as the user zooms in and out.

Snap behavior

When a user drags or resizes a task bar, ApexGantt snaps the task boundaries to the nearest unit. This prevents tasks from landing on arbitrary timestamps and keeps the schedule clean.

snapUnit sets the unit of the grid: 'day', 'hour', or 'minute'. snapValue sets how many of those units form one snap interval. The default is 1, meaning every day (or hour, or minute). Set it higher to enforce coarser alignment.

const gantt = new ApexGantt(document.getElementById('gantt'), {
  series: tasks,

  // Snap to the nearest half-day boundary
  snapUnit: 'hour',
  snapValue: 12,
})

gantt.render()

Common patterns:

// Daily snap (default)
{ snapUnit: 'day', snapValue: 1 }

// Weekly snap: tasks align to week boundaries
{ snapUnit: 'day', snapValue: 7 }

// Two-hour snap for sub-day scheduling
{ snapUnit: 'hour', snapValue: 2 }

// 15-minute snap for fine scheduling
{ snapUnit: 'minute', snapValue: 15 }

Snap only applies when enableTaskDrag or enableTaskResize is true. If both are false, the snap settings have no visible effect.