Skip to content

Gantt Timeline and Zoom

The Gantt timeline can be configured at two levels:

  1. zoomPreset for fast setup with built-in levels.
  2. zoom for full control over levels, wheel behavior, and anchor strategy.
Source code
TypeScript ts
// src/components/gantt/GanttShowcase.ts
import { defineCustomElements } from '@revolist/revogrid/loader';
defineCustomElements();

import {
  GanttPlugin,
  defineGanttToolbar,
} from '@revolist/revogrid-enterprise';
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
import {
  STANDARD_CALENDAR,
  SHOWCASE_ASSIGNMENTS,
  SHOWCASE_BASELINES,
  SHOWCASE_COLUMNS_POLISHED,
  SHOWCASE_DEPENDENCIES,
  SHOWCASE_GANTT_CONFIG,
  SHOWCASE_RESOURCES,
  SHOWCASE_TASKS,
  SHOWCASE_TOOLBAR_COLUMNS,
  getShowcaseTaskBarColor,
  renderShowcaseTaskBarContent,
} from './gantt-project-data';
import { currentTheme } from '../composables/useRandomData';
import './gantt-showcase.css';

// ─── Entry point ──────────────────────────────────────────────────────────────

export function load(parentSelector: string): void {
  const parent = document.querySelector(parentSelector);
  if (!parent) return;
  const darkTheme = currentTheme().isDark();

  const container = document.createElement('div');
  container.className = `gantt-showcase-shell grow h-full ${darkTheme ? 'gantt-showcase-shell--dark' : 'gantt-showcase-shell--light'}`;
  parent.appendChild(container);

  const grid = document.createElement('revo-grid') as HTMLRevoGridElement;
  const toolbar = document.createElement('div');
  container.appendChild(toolbar);

  grid.theme          = darkTheme ? 'darkCompact' : 'compact';
  grid.readonly       = false;
  grid.range          = true;
  grid.resize         = true;
  grid.rowHeaders     = true;
  grid.hideAttribution = true;
  grid.autoSizeColumn = true;
  grid.classList.add('gantt-showcase-grid');
  grid.plugins        = [GanttPlugin, ExportExcelPlugin];
  grid.columns        = [...SHOWCASE_COLUMNS_POLISHED];
  grid.source         = [...SHOWCASE_TASKS];
  grid.ganttDependencies = [...SHOWCASE_DEPENDENCIES];
  grid.ganttCalendars    = [{ ...STANDARD_CALENDAR }];
  grid.ganttResources    = [...SHOWCASE_RESOURCES];
  grid.ganttAssignments  = [...SHOWCASE_ASSIGNMENTS];
  grid.ganttBaselines    = [...SHOWCASE_BASELINES];
  grid.gantt = {
    ...SHOWCASE_GANTT_CONFIG,
    visuals: {
      ...SHOWCASE_GANTT_CONFIG.visuals,
      taskBarColorHook:    getShowcaseTaskBarColor,
      taskBarContentHook:  renderShowcaseTaskBarContent,
    },
  } as typeof grid.gantt;

  container.appendChild(grid);
  defineGanttToolbar(toolbar, {
    grid,
    columns: SHOWCASE_TOOLBAR_COLUMNS,
  });
}
React tsx
// src/components/gantt/GanttShowcase.tsx
import React, { useEffect, useRef } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import {
  GanttPlugin,
  defineGanttToolbar,
} from '@revolist/revogrid-enterprise';
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
import {
  STANDARD_CALENDAR,
  SHOWCASE_ASSIGNMENTS,
  SHOWCASE_BASELINES,
  SHOWCASE_COLUMNS_POLISHED,
  SHOWCASE_DEPENDENCIES,
  SHOWCASE_GANTT_CONFIG,
  SHOWCASE_RESOURCES,
  SHOWCASE_TASKS,
  SHOWCASE_TOOLBAR_COLUMNS,
  getShowcaseTaskBarColor,
  renderShowcaseTaskBarContent,
} from './gantt-project-data';
import type { GanttPluginConfig } from '@revolist/revogrid-enterprise';
import { currentTheme } from '../composables/useRandomData';
import './gantt-showcase.css';

const plugins = [GanttPlugin, ExportExcelPlugin];
const source      = [...SHOWCASE_TASKS];
const dependencies = [...SHOWCASE_DEPENDENCIES];
const calendars    = [{ ...STANDARD_CALENDAR }];
const resources    = [...SHOWCASE_RESOURCES];
const assignments  = [...SHOWCASE_ASSIGNMENTS];
const baselines    = [...SHOWCASE_BASELINES];
const columns      = [...SHOWCASE_COLUMNS_POLISHED];
const ganttConfig: GanttPluginConfig = {
  ...SHOWCASE_GANTT_CONFIG,
  visuals: {
    ...SHOWCASE_GANTT_CONFIG.visuals,
    taskBarColorHook: getShowcaseTaskBarColor,
    taskBarContentHook: renderShowcaseTaskBarContent,
  },
} as GanttPluginConfig;

function GanttShowcase() {
  const { isDark } = currentTheme();
  const darkTheme = isDark();
  const gridRef = useRef<HTMLRevoGridElement>(null);
  const toolbarRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const toolbar = toolbarRef.current;
    const grid = gridRef.current;
    if (!toolbar || !grid) {
      return undefined;
    }

    defineGanttToolbar(toolbar, {
      grid,
      columns: SHOWCASE_TOOLBAR_COLUMNS,
    });

    return () => {
      toolbar.textContent = '';
    };
  }, []);

  return (
    <div className={`gantt-showcase-shell grow h-full ${darkTheme ? 'gantt-showcase-shell--dark' : 'gantt-showcase-shell--light'}`}>
      <div ref={toolbarRef} />
      <RevoGrid
        ref={gridRef}
        className="gantt-showcase-grid"
        theme={darkTheme ? 'darkCompact' : 'compact'}
        hideAttribution
        readonly={false}
        range
        resize
        rowHeaders
        plugins={plugins}
        source={source}
        columns={columns}
        gantt={ganttConfig}
        ganttDependencies={dependencies}
        ganttCalendars={calendars}
        ganttResources={resources}
        ganttAssignments={assignments}
        ganttBaselines={baselines}
      />
    </div>
  );
}

export default GanttShowcase;
Vue vue
<template>
  <div :class="shellClass">
    <div ref="toolbarRef"></div>
    <RevoGrid
      ref="gridRef"
      class="gantt-showcase-grid skip-style cell-border"
      hide-attribution
      :readonly="false"
      :range="true"
      :resize="true"
      :row-headers="true"
      :theme="gridTheme"
      :plugins="plugins"
      :source="source"
      :columns="columns"
      :gantt.prop="ganttConfig"
      :gantt-dependencies.prop="dependencies"
      :gantt-calendars.prop="calendars"
      :gantt-resources.prop="resources"
      :gantt-assignments.prop="assignments"
      :gantt-baselines.prop="baselines"
    />
  </div>
</template>

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
import {
  STANDARD_CALENDAR,
  SHOWCASE_ASSIGNMENTS,
  SHOWCASE_BASELINES,
  SHOWCASE_COLUMNS_POLISHED,
  SHOWCASE_DEPENDENCIES,
  SHOWCASE_GANTT_CONFIG,
  SHOWCASE_RESOURCES,
  SHOWCASE_TASKS,
  SHOWCASE_TOOLBAR_COLUMNS,
  getShowcaseTaskBarColor,
  renderShowcaseTaskBarContent,
} from './gantt-project-data';
import { currentThemeVue } from '../composables/useRandomData';
import './gantt-showcase.css';

// ── Static grid data ──────────────────────────────────────────────────────────
const plugins = ref<unknown[]>([]);
const source      = ref([...SHOWCASE_TASKS]);
const dependencies = ref([...SHOWCASE_DEPENDENCIES]);
const calendars    = ref([{ ...STANDARD_CALENDAR }]);
const resources    = ref([...SHOWCASE_RESOURCES]);
const assignments  = ref([...SHOWCASE_ASSIGNMENTS]);
const baselines    = ref([...SHOWCASE_BASELINES]);
const columns      = ref([...SHOWCASE_COLUMNS_POLISHED]);
const { isDark } = currentThemeVue();
const gridTheme = computed(() => (isDark.value ? 'darkCompact' : 'compact'));
const shellClass = computed(() => [
  'gantt-showcase',
  'gantt-showcase-shell',
  'grow',
  'h-full',
  isDark.value ? 'gantt-showcase-shell--dark' : 'gantt-showcase-shell--light',
]);

const ganttConfig = ref({
  ...SHOWCASE_GANTT_CONFIG,
  visuals: {
    ...SHOWCASE_GANTT_CONFIG.visuals,
    taskBarColorHook:    getShowcaseTaskBarColor,
    taskBarContentHook:  renderShowcaseTaskBarContent,
  },
});

// ── Refs ──────────────────────────────────────────────────────────────────────
const gridRef    = ref<InstanceType<typeof RevoGrid> | HTMLRevoGridElement | null>(null);
const toolbarRef = ref<HTMLElement | null>(null);
let toolbarFrame = 0;

function getGridEl(): HTMLRevoGridElement | null {
  const refValue = gridRef.value as (InstanceType<typeof RevoGrid> & { $el?: HTMLRevoGridElement }) | HTMLRevoGridElement | null;
  const grid = (refValue && '$el' in refValue ? refValue.$el : refValue) as HTMLRevoGridElement | null;
  return grid ?? toolbarRef.value?.parentElement?.querySelector<HTMLRevoGridElement>('revo-grid') ?? null;
}

onMounted(async () => {
  const {
    GanttPlugin,
    defineGanttToolbar,
  } = await import('@revolist/revogrid-enterprise');

  plugins.value = [GanttPlugin, ExportExcelPlugin];
  await nextTick();

  toolbarFrame = requestAnimationFrame(() => {
    const grid = getGridEl();
    if (!toolbarRef.value || !grid) {
      return;
    }

    defineGanttToolbar(toolbarRef.value, {
      grid,
      columns: SHOWCASE_TOOLBAR_COLUMNS,
    });
  });
});

onBeforeUnmount(() => {
  cancelAnimationFrame(toolbarFrame);
  if (toolbarRef.value) {
    toolbarRef.value.textContent = '';
  }
});
</script>

<style scoped>
.gantt-showcase :deep(revo-grid) {
  flex: 1;
  min-height: 0;
}
</style>
Angular ts
// src/components/gantt/GanttShowcaseAngular.ts
import {
  Component,
  NO_ERRORS_SCHEMA,
  ViewChild,
  ElementRef,
  ViewEncapsulation,
  AfterViewInit,
} from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import {
  GanttPlugin,
  defineGanttToolbar,
} from '@revolist/revogrid-enterprise';
import { ExportExcelPlugin } from '@revolist/revogrid-pro';
import type { GanttPluginConfig } from '@revolist/revogrid-enterprise';
import {
  STANDARD_CALENDAR,
  SHOWCASE_ASSIGNMENTS,
  SHOWCASE_BASELINES,
  SHOWCASE_COLUMNS_POLISHED,
  SHOWCASE_DEPENDENCIES,
  SHOWCASE_GANTT_CONFIG,
  SHOWCASE_RESOURCES,
  SHOWCASE_TASKS,
  SHOWCASE_TOOLBAR_COLUMNS,
  getShowcaseTaskBarColor,
  renderShowcaseTaskBarContent,
} from './gantt-project-data';
import { currentTheme } from '../composables/useRandomData';

const ganttConfig: GanttPluginConfig = {
  ...SHOWCASE_GANTT_CONFIG,
  visuals: {
    ...SHOWCASE_GANTT_CONFIG.visuals,
    taskBarColorHook:    getShowcaseTaskBarColor,
    taskBarContentHook:  renderShowcaseTaskBarContent,
  },
} as GanttPluginConfig;

@Component({
  selector: 'gantt-showcase-grid',
  standalone: true,
  host: {
    class: 'gantt-showcase-angular-host',
  },
  // Allows Angular demos to bind RevoGrid plugin props that are not wrapper inputs.
  schemas: [NO_ERRORS_SCHEMA],
  imports: [RevoGrid],
  encapsulation: ViewEncapsulation.None,
  styleUrls: ['./gantt-showcase.css'],
  template: `
    <div [class]="shellClass">
      <div #toolbar></div>
      <revo-grid
        #grid
        class="gantt-showcase-grid skip-style cell-border"
        [theme]="theme"
        [hideAttribution]="true"
        [readonly]="false"
        [range]="true"
        [resize]="true"
        [rowHeaders]="true"
        [plugins]="plugins"
        [source]="source"
        [columns]="columns"
        [gantt]="ganttConfig"
        [ganttDependencies]="dependencies"
        [ganttCalendars]="calendars"
        [ganttResources]="resources"
        [ganttAssignments]="assignments"
        [ganttBaselines]="baselines"
      ></revo-grid>
    </div>
  `,
})
export class GanttShowcaseGridComponent implements AfterViewInit {
  @ViewChild('grid',    { read: ElementRef }) gridRef!:    ElementRef<HTMLRevoGridElement>;
  @ViewChild('toolbar', { read: ElementRef }) toolbarRef!: ElementRef<HTMLElement>;

  readonly isDark       = currentTheme().isDark();
  readonly theme        = this.isDark ? 'darkCompact' : 'compact';
  readonly shellClass   = `gantt-showcase-shell grow h-full ${this.isDark ? 'gantt-showcase-shell--dark' : 'gantt-showcase-shell--light'}`;
  readonly plugins      = [GanttPlugin, ExportExcelPlugin];
  readonly ganttConfig  = ganttConfig;
  readonly source       = [...SHOWCASE_TASKS];
  readonly dependencies = [...SHOWCASE_DEPENDENCIES];
  readonly calendars    = [{ ...STANDARD_CALENDAR }];
  readonly resources    = [...SHOWCASE_RESOURCES];
  readonly assignments  = [...SHOWCASE_ASSIGNMENTS];
  readonly baselines    = [...SHOWCASE_BASELINES];
  readonly columns      = [...SHOWCASE_COLUMNS_POLISHED];

  ngAfterViewInit(): void {
    defineGanttToolbar(this.toolbarRef.nativeElement, {
      grid: this.gridRef.nativeElement,
      columns: SHOWCASE_TOOLBAR_COLUMNS,
    });
  }
}

Use a built-in preset when you only need a standard timeline scale:

grid.gantt = {
// ...required project fields
zoomPreset: 'week-month',
};

Supported presets:

  • 'minute-hour' (15-minute ticks grouped by hour)
  • 'hour-day' (hour ticks grouped by day)
  • 'day-week'
  • 'week-month'
  • 'month-quarter'
  • 'quarter-year'
  • 'year-quarter'
  • 'multi-year-quarter'

By default, the interactive zoom ladder starts at day-week and excludes the intraday presets for performance. Use zoomPreset: 'hour-day', zoomPreset: 'minute-hour', or explicit zoom.levels when an intraday timeline is required.

Gantt timeline modes are defined by tickUnit + headerRows in each zoom level.

  • minute-hour: 15-minute timeline resolution for intraday planning windows.
  • hour-day: hourly timeline resolution grouped under day headers.
  • day-week: daily planning view.
  • week-month: weekly planning view.
  • month-quarter: monthly planning view.
  • quarter-year, year-quarter, multi-year-quarter: portfolio and long-range views.

Recommended usage pattern:

  • Opt into minute-hour for short execution windows and handoffs.
  • Opt into hour-day for same-day and next-day coordination.
  • Use day-week or coarser for baseline and milestone planning.

Use zoom when you need custom levels, min/max bounds, locale, or wheel interaction rules.

grid.gantt = {
// ...required project fields
zoomPreset: 'week-month',
zoom: {
enabled: true,
defaultLevelId: 'hour-day',
minLevelId: 'minute-hour',
maxLevelId: 'quarter-year',
locale: 'en-US',
zoomAnchorMode: 'pointer',
wheelZoomEnabled: true,
wheelZoomTrigger: 'ctrlKey',
wheelZoomMode: 'discrete',
invertWheelDirection: false,
},
};

You can define your own zoom.levels list from finest to coarsest.

grid.gantt = {
// ...required project fields
zoom: {
levels: [
{
id: 'minute-hour',
label: '15 Min / Hour',
tickUnit: 'minute',
tickCount: 15,
tickWidth: 44,
headerRows: [
{ id: 'day', unit: 'day' },
{ id: 'hour', unit: 'hour' },
{ id: 'minute', unit: 'minute', count: 15 },
],
},
{
id: 'hour-day',
label: 'Hour / Day',
tickUnit: 'hour',
tickWidth: 52,
headerRows: [
{ id: 'day', unit: 'day' },
{ id: 'hour', unit: 'hour' },
],
},
{
id: 'day-week',
label: 'Day / Week',
tickUnit: 'day',
tickWidth: 56,
headerRows: [
{ id: 'week', unit: 'week' },
{ id: 'day', unit: 'day' },
],
},
{
id: 'week-month',
label: 'Week / Month',
tickUnit: 'week',
tickWidth: 84,
headerRows: [
{ id: 'month', unit: 'month' },
{ id: 'week', unit: 'week' },
],
},
{
id: 'month-quarter',
label: 'Month / Quarter',
tickUnit: 'month',
tickWidth: 120,
headerRows: [
{ id: 'year', unit: 'year' },
{ id: 'month', unit: 'month' },
],
},
],
defaultLevelId: 'hour-day',
},
};

Control whether users can zoom with the mouse wheel and which modifier key is required.

grid.gantt = {
// ...required project fields
zoom: {
wheelZoomEnabled: true,
wheelZoomTrigger: 'metaKey', // macOS Cmd key
wheelZoomMode: 'smooth-discrete',
invertWheelDirection: false,
},
};

Available wheelZoomTrigger values:

  • 'ctrlKey'
  • 'metaKey'
  • 'altKey'
  • 'shiftKey'
  • 'none'

zoomAnchorMode controls which point in the viewport stays stable when switching levels:

  • 'pointer': keeps the date under the mouse pointer fixed.
  • 'center': keeps the center date fixed.
  • 'start': keeps the left edge date fixed.
grid.gantt = {
// ...required project fields
zoom: {
zoomAnchorMode: 'center',
},
};

Timeline visuals are configured in visuals.

grid.gantt = {
// ...required project fields
visuals: {
projectLineDate: '2026-04-06',
timeRanges: [
{
id: 'sprint-1',
startDate: '2026-04-06',
endDate: '2026-04-17',
label: 'Sprint 1',
color: '#5B8DEF',
},
{
id: 'freeze',
startDate: '2026-05-18',
endDate: '2026-05-22',
label: 'Code Freeze',
color: '#F59E0B',
},
],
shadeNonWorkingTime: true,
},
};