Syncfusion AI Assistant

How can I help you?

Virtual Scroll in React Gantt Chart Component

30 Apr 202624 minutes to read

Virtual scrolling in the React Gantt Chart component enhances performance by rendering only visible tasks and timeline segments, minimizing DOM operations for large datasets or extensive timelines. It includes row virtualization for handling thousands of tasks (e.g., 10,000 tasks in a project) and timeline virtualization for wide timelines (e.g., multi-year projects), both requiring VirtualScroll injection. Row virtualization renders tasks within the viewport, while timeline virtualization loads timeline cells on-demand during horizontal scrolling, ensuring efficient rendering for complex project management.

Configure row virtualization

Row virtualization, enabled by setting enableVirtualization to true, renders only tasks visible in the Gantt’s viewport, determined by the height property in pixels (e.g., “600px”). All tasks are fetched initially but rendered on-demand during vertical scrolling, reducing load times for large datasets. For example, a project with 10,000 tasks renders only the 50 visible tasks, improving performance. Inject VirtualScroll in the component’s providers to enable this feature. Ensure the height property is set explicitly to control the viewport size.

The following example enables row virtualization for a large dataset:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { GanttComponent, ColumnsDirective, ColumnDirective, Inject, Selection, VirtualScroll } from '@syncfusion/ej2-react-gantt';
import { virtualData } from './datasource';

function App() {
    const taskFields = {
        id: 'TaskID',
        name: 'TaskName',
        startDate: 'StartDate',
        endDate: 'EndDate',
        duration: 'Duration',
        progress: 'Progress',
        parentID: 'ParentID'
    };
    const splitterSettings = {
        columnIndex: 2
    };
    const labelSettings = {
        leftLabel: 'TaskName',
        taskLabel: 'Progress'
    };
    return (
        <GanttComponent dataSource={virtualData} treeColumnIndex={1} labelSettings={labelSettings}
            allowSelection={true} highlightWeekends={true} enableVirtualization={true}
            taskFields={taskFields} splitterSettings={splitterSettings} height='450px'>
            <ColumnsDirective>
                <ColumnDirective field='TaskID' />
                <ColumnDirective field='TaskName' headerText='Task Name' />
                <ColumnDirective field='StartDate' />
                <ColumnDirective field='Duration' />
                <ColumnDirective field='Progress' />
            </ColumnsDirective>
            <Inject services={[Selection, VirtualScroll]} />
        </GanttComponent>
    )
};
ReactDOM.render(<App />, document.getElementById('root'));
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { GanttComponent, ColumnsDirective, ColumnDirective, Inject, Selection, VirtualScroll, LabelSettingsModel, SplitterSettingsModel, TaskFieldsModel } from '@syncfusion/ej2-react-gantt';
import { virtualData } from './datasource';

function App() {
    const taskFields: TaskFieldsModel = {
        id: 'TaskID',
        name: 'TaskName',
        startDate: 'StartDate',
        endDate: 'EndDate',
        duration: 'Duration',
        progress: 'Progress',
        parentID: 'ParentID'
    };
    const splitterSettings: SplitterSettingsModel = {
        columnIndex: 2
    };
    const labelSettings: LabelSettingsModel = {
        leftLabel: 'TaskName',
        taskLabel: 'Progress'
    };
    return (
        <GanttComponent dataSource={virtualData} treeColumnIndex={1} labelSettings={labelSettings}
            allowSelection={true} highlightWeekends={true} enableVirtualization={true}
            taskFields={taskFields} splitterSettings={splitterSettings} height='450px'>
            <ColumnsDirective>
                <ColumnDirective field='TaskID' />
                <ColumnDirective field='TaskName' headerText='Task Name' />
                <ColumnDirective field='StartDate' />
                <ColumnDirective field='Duration' />
                <ColumnDirective field='Progress' />
            </ColumnsDirective>
            <Inject services={[Selection, VirtualScroll]} />
        </GanttComponent>
    )
};
ReactDOM.render(<App />, document.getElementById('root'));
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Syncfusion React Gantt</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Essential JS 2 for React Components" />
    <meta name="author" content="Syncfusion" />
    <link href="https://cdn.syncfusion.com/ej2/33.2.3/tailwind3.css" rel="stylesheet" type="text/css" />
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
    <style>
        #loader {
            color: #008cff;
            height: 40px;
            left: 45%;
            position: absolute;
            top: 45%;
            width: 30%;
        }
    </style>
</head>

<body>
    <div id='root'>
        <div id='loader'>Loading....</div>
    </div>
</body>

</html>

Configure timeline virtualization

Timeline virtualization, enabled by setting enableTimelineVirtualization to true, renders three times the Gantt’s width initially, loading additional timeline cells during horizontal scrolling. This optimizes performance for wide timelines, such as multi-year projects, by rendering only visible segments. It depends on timelineSettings for scale (e.g., monthly or daily tiers). Inject VirtualScroll to enable this feature.

The following example enables timeline virtualization for a wide timeline:

let tempData = [
    {
        TaskID: 1, TaskName: 'Product concept',StartDate: new Date('04/02/2019'), EndDate: new Date('04/21/2019'),
        ParentID: 0
    },
    {
        TaskID: 2, TaskName: 'Defining the product and its usage', StartDate: new Date('04/02/2019'),
        Duration: 3, Progress: 30, ParentID: 1
    },
    {
        TaskID: 3, TaskName: 'Defining target audience', StartDate: new Date('04/02/2019'),
        ParentID: 1, Duration: 3
    },
    {
        TaskID: 4, TaskName: 'Prepare product sketch and notes', StartDate: new Date('04/05/2019'),
        Duration: 2, ParentID: 1, Progress: 30
    },
    {
        TaskID: 5, TaskName: 'Concept approval', StartDate: new Date('04/08/2019'),
        ParentID: 0, Duration: 0
    },
    {
        TaskID: 6, TaskName: 'Market research', StartDate: new Date('04/02/2019'),
        ParentID: 0, EndDate: new Date('04/21/2019')
    },
    {
        TaskID: 7, TaskName: 'Demand analysis', StartDate: new Date('04/04/2019'),
        EndDate: new Date('04/21/2019'), ParentID: 6
    },
    {
        TaskID: 8, TaskName: 'Customer strength', StartDate: new Date('04/09/2019'),
        Duration: 4, ParentID: 7, Progress: 30
    },
    {
        TaskID: 9, TaskName: 'Market opportunity analysis', StartDate: new Date('04/09/2019'),
        Duration: 4, ParentID: 7
    },
    {
        TaskID: 10, TaskName: 'Competitor analysis', StartDate: new Date('04/15/2019'),
        Duration: 4, ParentID: 6, Progress: 30
    },
    {
        TaskID: 11, TaskName: 'Product strength analsysis', StartDate: new Date('04/15/2019'),
        Duration: 4, ParentID: 6
    },
    {
        TaskID: 12, TaskName: 'Research complete', StartDate: new Date('04/18/2019'),
        Duration: 0, ParentID: 6
    },
    {
        TaskID: 13, TaskName: 'Product design and development', StartDate: new Date('04/04/2019'),
        ParentID: 0, EndDate: new Date('04/21/2019')
    },
    {
        TaskID: 14, TaskName: 'Functionality design', StartDate: new Date('04/19/2019'),
        Duration: 3, ParentID: 13, Progress: 30
    },
    {
        TaskID: 15, TaskName: 'Quality design', StartDate: new Date('04/19/2019'),
        Duration: 3, ParentID: 13
    },
    {
        TaskID: 16, TaskName: 'Define reliability', StartDate: new Date('04/24/2019'),
        Duration: 2, Progress: 30, ParentID: 13
    },
    {
        TaskID: 17, TaskName: 'Identifying raw materials', StartDate: new Date('04/24/2019'),
        Duration: 2, ParentID: 13
    },
    {
        TaskID: 18, TaskName: 'Define cost plan', StartDate: new Date('04/04/2019'),
        ParentID: 13, EndDate: new Date('04/21/2019')
    },
    {
        TaskID: 19, TaskName: 'Manufacturing cost', StartDate: new Date('04/26/2019'),
        Duration: 2, Progress: 30, ParentID: 18
    },
    {
        TaskID: 20, TaskName: 'Selling cost', StartDate: new Date('04/26/2019'),
        Duration: 2, ParentID: 18
    },
    {
        TaskID: 21, TaskName: 'Development of the final design', StartDate: new Date('04/30/2019'),
        ParentID: 13, EndDate: new Date('04/21/2019')
    },
    {
        TaskID: 22, TaskName: 'Defining dimensions and package volume', StartDate: new Date('04/30/2019'),
        Duration: 2, ParentID: 21, Progress: 30
    },
    {
        TaskID: 23, TaskName: 'Develop design to meet industry standards', StartDate: new Date('05/02/2019'),
        Duration: 2, ParentID: 21
    },
    {
        TaskID: 24, TaskName: 'Include all the details', StartDate: new Date('05/06/2019'),
        Duration: 3, ParentID: 21
    },
    {
        TaskID: 25, TaskName: 'CAD computer-aided design', StartDate: new Date('05/09/2019'),
        Duration: 3, ParentID: 13, Progress: 30
    },
    {
        TaskID: 26, TaskName: 'CAM computer-aided manufacturing', StartDate: new Date('09/14/2019'),
        Duration: 3, ParentID: 13
    },
    {
        TaskID: 27, TaskName: 'Design complete', StartDate: new Date('05/16/2019'),
        Duration: 0, ParentID: 13
    },
    {
        TaskID: 28, TaskName: 'Prototype testing', StartDate: new Date('05/17/2019'),
        Duration: 4, Progress: 30, ParentID: 0
    },
    {
        TaskID: 29, TaskName: 'Include feedback', StartDate: new Date('05/17/2019'),
        Duration: 4, ParentID: 0
    },
    {
        TaskID: 30, TaskName: 'Manufacturing', StartDate: new Date('05/23/2019'),
        Duration: 5, Progress: 30, ParentID: 0
    },
    {
        TaskID: 31, TaskName: 'Assembling materials to finsihed goods', StartDate: new Date('05/30/2019'),
        Duration: 5, ParentID: 0
    },
    {
        TaskID: 32, TaskName: 'Feedback and testing', StartDate: new Date('04/04/2019'),
        ParentID: 0, EndDate: new Date('04/21/2019'),
    },
    {
        TaskID: 33, TaskName: 'Internal testing and feedback', StartDate: new Date('06/06/2019'),
        Duration: 3, ParentID: 32, Progress: 45
    },
    {
        TaskID: 34, TaskName: 'Customer testing and feedback', StartDate: new Date('06/11/2019'),
        Duration: 3, ParentID: 32, Progress: 50
    },
    {
        TaskID: 35, TaskName: 'Final product development', StartDate: new Date('04/04/2019'),
        ParentID: 0, EndDate: new Date('04/21/2019'),
    },
    {
        TaskID: 36, TaskName: 'Important improvements', StartDate: new Date('06/14/2019'),
        Duration: 4, Progress: 30, ParentID: 35
    },
    {
        TaskID: 37, TaskName: 'Address any unforeseen issues', StartDate: new Date('06/14/2019'),
        Duration: 4, Progress: 30, ParentID: 35
    },
    {
        TaskID: 38, TaskName: 'Final product', StartDate: new Date('04/04/2019'),
        ParentID: 0, EndDate: new Date('04/21/2019'),
    },
    {
        TaskID: 39, TaskName: 'Branding product', StartDate: new Date('06/20/2019'),
        Duration: 4, ParentID: 38
    },
    {
        TaskID: 40, TaskName: 'Marketing and presales', StartDate: new Date('06/26/2019'), Duration: 4,
        Progress: 30, ParentID: 38
    }
];

let virtualData = [];
let projId = 1;
for (let i = 0; i < 50; i++) {
    let x = virtualData.length + 1;
    let parent = {};
    /* tslint:disable:no-string-literal */
    parent['TaskID'] = x;
    parent['TaskName'] = 'Project ' + (i + 1);
    virtualData.push(parent);
    for (let j = 0; j < tempData.length; j++) {
        let subtasks = {};
        /* tslint:disable:no-string-literal */
        subtasks['TaskID'] = tempData[j].TaskID + x;
        subtasks['TaskName'] = tempData[j].TaskName;
        subtasks['StartDate'] = tempData[j].StartDate;
        subtasks['Duration'] = tempData[j].Duration;
        subtasks['Progress'] = tempData[j].Progress;
        subtasks['ParentID'] = tempData[j].ParentID + x;
        virtualData.push(subtasks);
    }
}
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { GanttComponent,ColumnsDirective,ColumnDirective,Inject,Edit, Selection, Toolbar, VirtualScroll } from '@syncfusion/ej2-react-gantt';

function App (){
    const taskFields = {
    id: 'TaskID',
    name: 'TaskName',
    startDate: 'StartDate',
    endDate: 'EndDate',
    duration: 'Duration',
    progress: 'Progress',
    parentID: 'ParentID'
  };
  const splitterSettings= {
    columnIndex: 2
  };
const labelSettings = {
    taskLabel: 'Progress'
  };
  const editSettings = {
    allowAdding: true,
    allowEditing: true,
    allowDeleting: true,
    allowTaskbarEditing: true,
    showDeleteConfirmDialog: true
  };
  const projectStartDate = new Date('04/01/2019');
  const projectEndDate = new Date('12/31/2025');
  const toolbar = ['Add', 'Edit', 'Update', 'Delete', 'Cancel', 'ExpandAll', 'CollapseAll', 'Indent', 'Outdent'];
  return (
          <GanttComponent dataSource={virtualData} treeColumnIndex={1} labelSettings={labelSettings} projectEndDate={projectEndDate}
            allowSelection={true} highlightWeekends={true} enableVirtualization={true}  enableTimelineVirtualization={true} projectStartDate={projectStartDate}
            taskFields={taskFields} splitterSettings={splitterSettings} autoCalculateDateScheduling={false} toolbar={toolbar} editSettings={editSettings} height='450px'>
            <ColumnsDirective>
                    <ColumnDirective field='TaskID'/>
                    <ColumnDirective field='TaskName' headerText='Task Name'/>
                    <ColumnDirective field='StartDate'/>
                    <ColumnDirective field='Duration'/>
                    <ColumnDirective field='Progress'/>
                </ColumnsDirective>
            <Inject services={[Selection, VirtualScroll,Edit,Toolbar]} />
          </GanttComponent>
    )
};
ReactDOM.render(<App />, document.getElementById('root'));
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { GanttComponent, ColumnsDirective, ColumnDirective, Inject, Edit, Selection, Toolbar, VirtualScroll, SplitterSettingsModel, TaskFieldsModel, LabelSettingsModel, EditSettingsModel } from '@syncfusion/ej2-react-gantt';
import { virtualData } from './datasource';
function App() {
    const taskFields: TaskFieldsModel = {
        id: 'TaskID',
        name: 'TaskName',
        startDate: 'StartDate',
        endDate: 'EndDate',
        duration: 'Duration',
        progress: 'Progress',
        parentID: 'ParentID'
    };
    const splitterSettings: SplitterSettingsModel = {
        columnIndex: 2
    };
    const labelSettings: LabelSettingsModel = {
        taskLabel: 'Progress'
    };
    const editSettings: EditSettingsModel = {
        allowAdding: true,
        allowEditing: true,
        allowDeleting: true,
        allowTaskbarEditing: true,
        showDeleteConfirmDialog: true
    };
    const toolbar: Toolbar[] = ['Add', 'Edit', 'Update', 'Delete', 'Cancel', 'ExpandAll', 'CollapseAll', 'Indent', 'Outdent'];
    const projectStartDate: Date = new Date('04/01/2019');
    const projectEndDate: Date = new Date('12/31/2025');
    return (
        <GanttComponent dataSource={virtualData} treeColumnIndex={1} labelSettings={labelSettings} projectEndDate={projectEndDate} allowSelection={true} highlightWeekends={true} enableVirtualization={true} enableTimelineVirtualization={true} projectStartDate={projectStartDate} taskFields={taskFields} splitterSettings={splitterSettings} autoCalculateDateScheduling={false} toolbar={toolbar} editSettings={editSettings} height='450px'>
            <ColumnsDirective>
                <ColumnDirective field='TaskID' />
                <ColumnDirective field='TaskName' headerText='Task Name' />
                <ColumnDirective field='StartDate' />
                <ColumnDirective field='Duration' />
                <ColumnDirective field='Progress' />
            </ColumnsDirective>
            <Inject services={[Selection, VirtualScroll, Edit, Toolbar]} />
        </GanttComponent>
    )
};
ReactDOM.render(<App />, document.getElementById('root'));
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Syncfusion React Gantt</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Essential JS 2 for React Components" />
    <meta name="author" content="Syncfusion" />
    <link href="https://cdn.syncfusion.com/ej2/33.2.3/tailwind3.css" rel="stylesheet" type="text/css"/>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
     <style>
        #loader {
            color: #008cff;
            height: 40px;
            left: 45%;
            position: absolute;
            top: 45%;
            width: 30%;
        }
    </style>
</head>

<body>
        <div id='root'>
            <div id='loader'>Loading....</div>
        </div>
</body>

</html>

Virtual scroll limitations

Virtual scrolling has the following constraints:

  • Incompatible with enableImmutableMode, as both use different rendering optimizations.
  • Cell selection is not persisted due to on-demand rendering.
  • Browser height limits restrict the maximum number of records in row virtualization.
  • The height property must be set in pixels for row virtualization to define the viewport size.
  • Set a static height for the Gantt chart or its parent container; 100% height only works if both the component and its parent have explicit static heights.
  • With virtualization enabled, data is rendered in pages. When scrolling to load the next set of records, only the current page’s data is available to Gantt’s public methods. If a record is selected during this process, only the visible page records are returned. This behavior occurs because the Gantt chart does not retain data from all pages in memory, optimizing performance by loading only the required set.

See also