Column Spanning in Angular Gantt Chart Component

18 Nov 201820 minutes to read

The Angular Gantt Chart component supports column spanning, allowing adjacent cells to merge horizontally for improved layout clarity. This feature is useful for grouping related data or enhancing visual structure.

To enable column spanning, use the queryCellInfo event and set the colSpan property to define how many columns a cell should span.

In the following example, Work cells are spanned to improve visual clarity:

import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { GanttModule } from '@syncfusion/ej2-angular-gantt';
import { GanttData } from './data';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [GanttModule],
  encapsulation: ViewEncapsulation.None,
  template: `
    <ejs-gantt height="430px" [dataSource]="data" [taskFields]="taskSettings" [treeColumnIndex]="1" [splitterSettings]="splitterSettings" [gridLines]="gridLines" (queryCellInfo)="queryCellInfoEvent($event)">
      <e-columns>
        <e-column field="TaskID" headerText="Task ID" textAlign="Right" width="90"></e-column>
        <e-column field="TaskName" headerText="Task Name" textAlign="Left" width="290"></e-column>
        <e-column field="work1" headerText="Work 1" textAlign="Center" width="150"></e-column>
        <e-column field="work2" headerText="Work 2" textAlign="Center" width="150"></e-column>
        <e-column field="work3" headerText="Work 3" textAlign="Center" width="150"></e-column>
        <e-column field="work4" headerText="Work 4" textAlign="Center" width="150"></e-column>
        <e-column field="StartDate" headerText="Start Date" textAlign="Right" width="120"></e-column>
        <e-column field="Duration" headerText="Duration" textAlign="Right" width="90"></e-column>
        <e-column field="Progress" headerText="Progress" textAlign="Right" width="120"></e-column>
      </e-columns>
    </ejs-gantt>`
})

export class AppComponent implements OnInit {
  public data?: object[];
  public taskSettings?: object;
  public splitterSettings?: object;
  public gridLines?: string;

  ngOnInit(): void {
    this.data = GanttData;
    this.taskSettings = {
      id: 'TaskID',
      name: 'TaskName',
      startDate: 'StartDate',
      duration: 'Duration',
      progress: 'Progress',
      child: 'subtasks'
    };
    this.gridLines = 'Both';
    this.splitterSettings = {
      position: '85%'
    };
  }

  public queryCellInfoEvent = (args: any): void => {
    const data = args.data;
    const field = args.column?.field;

    switch (data.TaskID) {
      case 1:
        if (field === 'work3' || field === 'work4') {
          args.colSpan = 1;
        } else if (field === 'work1') {
          args.colSpan = 2;
        }
        break;
      case 2:
        if (field === 'work1' || field === 'work4') {
          args.colSpan = 1;
        } else if (field === 'work2') {
          args.colSpan = 2;
        }
        break;
      case 3:
        if (field === 'work4') {
          args.colSpan = 1;
        } else if (field === 'work1') {
          args.colSpan = 3;
        }
        break;
      case 4:
        if (field === 'work4' || field === 'work1') {
          args.colSpan = 1;
        }
        break;
      case 5:
        if (field === 'work3') {
          args.colSpan = 2;
        } else if (field === 'work1') {
          args.colSpan = 2;
        }
        break;
      case 6:
        if (field === 'work4') {
          args.colSpan = 1;
        } else if (field === 'work1') {
          args.colSpan = 3;
        }
        break;
      case 7:
        if (field === 'work4' || field === 'work1') {
          args.colSpan = 1;
        }
        break;
      case 8:
        if (field === 'work1' || field === 'work4') {
          args.colSpan = 1;
        } else if (field === 'work2') {
          args.colSpan = 2;
        }
        break;
    }
  };
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));
export let GanttData: Object[]  = [
    {
        TaskID: 1,
        TaskName: 'Project Initiation',
        StartDate: new Date('04/02/2019'),
        EndDate: new Date('04/21/2019'),
        subtasks: [
            { TaskID: 2, TaskName: 'Identify Site location', StartDate: new Date('04/02/2019'), Duration: 4, Progress: 50, Verified: true },
            { TaskID: 3, TaskName: 'Perform Soil test', StartDate: new Date('04/02/2019'), Duration: 4, Progress: 50, },
            { TaskID: 4, TaskName: 'Soil test approval', StartDate: new Date('04/02/2019'), Duration: 4, Progress: 50, Verified: true },
        ]
    },
    {
        TaskID: 5,
        TaskName: 'Project Estimation',
        StartDate: new Date('04/02/2019'),
        EndDate: new Date('04/21/2019'),
        subtasks: [
            { TaskID: 6, TaskName: 'Develop floor plan for estimation', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50,  },
            { TaskID: 7, TaskName: 'List materials', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50, Verified: true},
            { TaskID: 8, TaskName: 'Estimation approval', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50, Verified: true }
        ]
    },
];

Customize border color of spanned columns

You can customize the border color of spanned cells using the queryCellInfo event. This event triggers before the cell is rendered, allowing you to apply custom styles dynamically.

The following example demonstrates how to change the border color of spanned cells:

import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { GanttModule, QueryCellInfoEventArgs } from '@syncfusion/ej2-angular-gantt';
import { GanttData } from './data';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [GanttModule],
  encapsulation: ViewEncapsulation.None,
  template: `
    <ejs-gantt height="430px" [dataSource]="data" [taskFields]="taskSettings" [treeColumnIndex]="1" [splitterSettings]="splitterSettings" [gridLines]="gridLines" (queryCellInfo)="queryCellInfoEvent($event)">
      <e-columns>
        <e-column field="TaskID" headerText="Task ID" textAlign="Right" width="90"></e-column>
        <e-column field="TaskName" headerText="Task Name" textAlign="Left" width="290"></e-column>
        <e-column field="work1" headerText="Work 1" textAlign="Center" width="150"></e-column>
        <e-column field="work2" headerText="Work 2" textAlign="Center" width="150"></e-column>
        <e-column field="work3" headerText="Work 3" textAlign="Center" width="150"></e-column>
        <e-column field="work4" headerText="Work 4" textAlign="Center" width="150"></e-column>
        <e-column field="StartDate" headerText="Start Date" textAlign="Right" width="120"></e-column>
        <e-column field="Duration" headerText="Duration" textAlign="Right" width="90"></e-column>
        <e-column field="Progress" headerText="Progress" textAlign="Right" width="120"></e-column>
      </e-columns>
    </ejs-gantt>`
})

export class AppComponent implements OnInit {
  public data?: object[];
  public taskSettings?: object;
  public splitterSettings?: object;
  public gridLines?: string;

  public ngOnInit(): void {
    this.data = GanttData;
    this.taskSettings = {
      id: 'TaskID',
      name: 'TaskName',
      startDate: 'StartDate',
      duration: 'Duration',
      progress: 'Progress',
      child: 'subtasks',
    };
    this.gridLines = 'Both';
    this.splitterSettings = {
      position: '85%'
    };
  }

  public queryCellInfoEvent = (args: QueryCellInfoEventArgs): void => {
    const datas = args.data as GanttTask;
    switch (datas.TaskID) {
      case 1:
        if (args.column?.field === 'work3' || args.column?.field === 'work4') {
          args.colSpan = 1;
        } else if (args.column?.field === 'work1') {
          args.colSpan = 2;
        }
        break;
      case 2:
        if (args.column?.field === 'work1' || args.column?.field === 'work4') {
          args.colSpan = 1;
        } else if (args.column?.field === 'work2') {
          args.colSpan = 2;
        }
        break;
      case 3:
        if (args.column?.field === 'work4') {
          args.colSpan = 1;
        } else if (args.column?.field === 'work1') {
          args.colSpan = 3;
        }
        break;
      case 4:
        if (args.column?.field === 'work4' || args.column?.field === 'work1') {
          args.colSpan = 1;
        }
        break;
      case 5:
        if (args.column?.field === 'work3') {
          args.colSpan = 2;
        } else if (args.column?.field === 'work1') {
          args.colSpan = 2;
        }
        break;
      case 6:
        if (args.column?.field === 'work4') {
          args.colSpan = 1;
        } else if (args.column?.field === 'work1') {
          args.colSpan = 3;
        }
        break;
      case 7:
        if (args.column?.field === 'work4' || args.column?.field === 'work1') {
          args.colSpan = 1;
        }
        break;
      case 8:
        if (args.column?.field === 'work1' || args.column?.field === 'work4') {
          args.colSpan = 1;
        } else if (args.column?.field === 'work2') {
          args.colSpan = 2;
        }
        break;
    }

    if ((args.colSpan ?? 0) > 1) {
      (args.cell as HTMLElement).style.border = '1px solid red';
    }
  };
}

export interface GanttTask {
  TaskID: number;
  TaskName: string;
  StartDate: Date;
  Duration: number;
  Progress: number;
  work1?: string;
  work2?: string;
  work3?: string;
  work4?: string;
  subtasks?: GanttTask[];
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));
export let GanttData: Object[]  = [
    {
        TaskID: 1,
        TaskName: 'Project Initiation',
        StartDate: new Date('04/02/2019'),
        EndDate: new Date('04/21/2019'),
        subtasks: [
            { TaskID: 2, TaskName: 'Identify Site location', StartDate: new Date('04/02/2019'), Duration: 4, Progress: 50, Verified: true },
            { TaskID: 3, TaskName: 'Perform Soil test', StartDate: new Date('04/02/2019'), Duration: 4, Progress: 50, },
            { TaskID: 4, TaskName: 'Soil test approval', StartDate: new Date('04/02/2019'), Duration: 4, Progress: 50, Verified: true },
        ]
    },
    {
        TaskID: 5,
        TaskName: 'Project Estimation',
        StartDate: new Date('04/02/2019'),
        EndDate: new Date('04/21/2019'),
        subtasks: [
            { TaskID: 6, TaskName: 'Develop floor plan for estimation', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50,  },
            { TaskID: 7, TaskName: 'List materials', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50, Verified: true},
            { TaskID: 8, TaskName: 'Estimation approval', StartDate: new Date('04/04/2019'), Duration: 3, Progress: 50, Verified: true }
        ]
    },
];

Limitations

Column spanning is not compatible with the following features:

  1. Virtual scrolling
  2. Infinite scrolling

Ensure these features are disabled when using column spanning to avoid rendering issues.