Scheduling & constraints
With autoScheduling the engine re-plans after every edit: a forward pass places each task at the earliest date its links (with lag or lead) and its constraint allow — pulling tasks earlier as well as pushing them later — and a backward pass slides ALAP tasks as late as their successors allow. Manually scheduled tasks never move; what cannot be satisfied is reported as a scheduling conflict.
Lag, lead & slack
Links carry a lag (negative = lead) in working days on the workCalendar or in hours, drawn as a badge on the arrow. Double-click an arrow — or select it and press Enter — to edit the type and lag; or type 2FS+2d into a Predecessors cell (double-click or F2). The slack columns come from the same backward pass that marks the critical path.
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { OgeGantt } from '@oge-ui/gantt';
import type { OgeGanttColumn, OgeGanttWorkCalendar } from '@oge-ui/gantt';
@Component({
selector: 'demo-root',
imports: [OgeGantt],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- lag (negative = lead) on any link type, in working days on the
calendar or in hours; auto-scheduling pulls tasks earlier as well as
pushing them later. Edit a link: double-click its arrow, or type
"2FS+2d" into the Predecessors cell. -->
<button type="button" (click)="gantt.scheduleProject()">Recalculate</button>
<oge-gantt
#gantt
[tasks]="tasks"
[dependencies]="links"
[columns]="columns"
[workCalendar]="calendar"
[autoScheduling]="true"
[showCriticalPath]="true"
[inlineEditing]="true"
style="height: 420px"
/>
`,
})
export class Demo {
protected readonly calendar: OgeGanttWorkCalendar = { workingDays: [1, 2, 3, 4, 5] };
protected readonly columns: OgeGanttColumn[] = [
{ field: 'wbs' },
{ field: 'title' },
{ field: 'start' },
{ field: 'predecessors' },
{ field: 'totalSlack' },
{ field: 'freeSlack' },
];
protected readonly tasks = [
{ id: 1, title: 'Specs', start: new Date(2026, 7, 3), end: new Date(2026, 7, 7) },
{ id: 2, title: 'Prototype', start: new Date(2026, 7, 12), end: new Date(2026, 7, 19) },
{ id: 3, title: 'User tests', start: new Date(2026, 7, 17), end: new Date(2026, 7, 21) },
{ id: 4, title: 'Release', start: new Date(2026, 7, 28), end: new Date(2026, 7, 28) },
];
protected readonly links = [
{ id: 'a', predecessorId: 1, successorId: 2, lag: 2 }, // FS + 2 working days
{ id: 'b', predecessorId: 2, successorId: 3, type: 'SS', lag: 3 },
{ id: 'c', predecessorId: 3, successorId: 4, lag: 4, lagUnit: 'hours' },
];
}Constraints, deadlines & conflicts
SNET and FNET set a floor, MSO and MFO pin a task, SNLT and FNLT cap it, ALAP slides it late. The security review cannot start before the 10th; the frontend overshoots its deadline flag; the manual vendor install ignores its link; go-live must finish on the 21st but its predecessors end later. Each conflict draws a dashed outline and a badge, joins the row's accessible name and arrives in schedulingConflict.
- Security review (SNET) breaks its constraint: Start no earlier than Aug 10, 2026
- Frontend (deadline) finishes after its deadline Aug 18, 2026
- Vendor install (manual) starts before its Finish-to-start link allows
- Go-live (must finish on) breaks its constraint: Must finish on Aug 21, 2026
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeGantt } from '@oge-ui/gantt';
import type { OgeGanttColumn, OgeGanttSchedulingConflictEvent } from '@oge-ui/gantt';
@Component({
selector: 'demo-root',
imports: [OgeGantt],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- constraintType + constraintDate (ASAP, ALAP, SNET, SNLT, FNET,
FNLT, MSO, MFO), a deadline marker and manual tasks the engine never
moves. Whatever the engine cannot satisfy comes back as a conflict:
a dashed outline, a "!" badge, the row label — and this event. -->
<oge-gantt
[tasks]="tasks"
[dependencies]="links"
[columns]="columns"
[autoScheduling]="true"
style="height: 420px"
(schedulingConflict)="onConflicts($event)"
/>
<ul aria-live="polite">
@for (message of messages(); track message) {
<li>{{ message }}</li>
}
</ul>
`,
})
export class Demo {
protected readonly messages = signal<string[]>([]);
protected readonly columns: OgeGanttColumn[] = [
{ field: 'title', widthPx: 200 },
{ field: 'constraint' },
{ field: 'deadline' },
];
protected readonly tasks = [
{ id: 1, title: 'Backend', start: new Date(2026, 7, 3), end: new Date(2026, 7, 12) },
{
id: 2,
title: 'Security review',
start: new Date(2026, 7, 3),
end: new Date(2026, 7, 5),
constraintType: 'SNET',
constraintDate: new Date(2026, 7, 10),
},
{
id: 3,
title: 'Frontend',
start: new Date(2026, 7, 12),
end: new Date(2026, 7, 19),
deadline: new Date(2026, 7, 18),
},
{ id: 4, title: 'Translations', start: new Date(2026, 7, 3), end: new Date(2026, 7, 5), constraintType: 'ALAP' },
{
id: 5,
title: 'Vendor install',
start: new Date(2026, 7, 10),
end: new Date(2026, 7, 12),
manuallyScheduled: true,
},
];
protected readonly links = [
{ id: 'a', predecessorId: 1, successorId: 3 },
{ id: 'b', predecessorId: 1, successorId: 5 }, // the manual task breaks this link
];
protected onConflicts(event: OgeGanttSchedulingConflictEvent<Record<string, unknown>>): void {
this.messages.set(event.conflicts.map((conflict) => conflict.message));
}
}Baselines, split tasks & progress line
Each task carries two baselines here — pick one in the toolbar chooser (or hide them); setBaseline(i) saves the current dates as baseline i. The data copy is a split task (two pieces). The progress line zig-zags through the status date to how far each started task really is, and the summary bar shows its child milestones.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeGantt } from '@oge-ui/gantt';
@Component({
selector: 'demo-root',
imports: [OgeGantt],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- several baselines (toolbar chooser), a split task, the progress
line through the status date and child milestones rolled up onto the
summary bar. setBaseline(i) saves the current dates as baseline i. -->
<button type="button" (click)="gantt.setBaseline(2)">Save as baseline 3</button>
<oge-gantt
#gantt
[tasks]="tasks"
[dependencies]="links"
[showProgressLine]="true"
[statusDate]="statusDate"
[showRollups]="true"
[(baselineIndex)]="baseline"
style="height: 360px"
/>
`,
})
export class Demo {
protected readonly baseline = signal(0);
protected readonly statusDate = new Date(2026, 7, 14);
protected readonly tasks = [
{ id: 1, title: 'Migration', start: new Date(2026, 7, 3), end: new Date(2026, 7, 3) },
{
id: 2,
parentId: 1,
title: 'Inventory',
start: new Date(2026, 7, 3),
end: new Date(2026, 7, 7),
progress: 100,
baselines: [
{ start: new Date(2026, 7, 3), end: new Date(2026, 7, 6) },
{ start: new Date(2026, 7, 4), end: new Date(2026, 7, 7) },
],
},
{ id: 3, parentId: 1, title: 'Plan approved', start: new Date(2026, 7, 7), end: new Date(2026, 7, 7) },
{
id: 4,
parentId: 1,
title: 'Data copy',
start: new Date(2026, 7, 10),
end: new Date(2026, 7, 19),
progress: 50,
segments: [
{ start: new Date(2026, 7, 10), end: new Date(2026, 7, 13) },
{ start: new Date(2026, 7, 17), end: new Date(2026, 7, 19) },
],
},
];
protected readonly links = [
{ id: 'a', predecessorId: 2, successorId: 3 },
{ id: 'b', predecessorId: 3, successorId: 4, lag: 1 },
];
}Quarter & year scales
Quarters (under year headers) and years (under decades) extend the zoom ladder for roadmaps. zoomPresets fills the toolbar's scale chooser — a scale plus an optional tick width and label.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { OgeGantt } from '@oge-ui/gantt';
import type { OgeGanttScaleType, OgeGanttZoomPreset } from '@oge-ui/gantt';
@Component({
selector: 'demo-root',
imports: [OgeGantt],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<!-- quarters and years extend the zoom ladder; zoomPresets fills the
toolbar's scale chooser (a scale plus an optional tick width). -->
<oge-gantt
[tasks]="tasks"
[zoomPresets]="presets"
[(scaleType)]="scale"
style="height: 300px"
/>
`,
})
export class Demo {
protected readonly scale = signal<OgeGanttScaleType>('quarters');
protected readonly presets: OgeGanttZoomPreset[] = [
{ scaleType: 'weeks', label: 'Sprint view' },
{ scaleType: 'months', tickWidth: 64, label: 'Compact months' },
{ scaleType: 'quarters', label: 'Quarters' },
{ scaleType: 'years', label: 'Roadmap (years)' },
];
protected readonly tasks = [
{ id: 1, title: 'Platform v2', start: new Date(2026, 0, 12), end: new Date(2026, 5, 30) },
{ id: 2, title: 'Mobile apps', start: new Date(2026, 3, 1), end: new Date(2026, 11, 18) },
{ id: 3, title: 'EU region', start: new Date(2026, 8, 1), end: new Date(2027, 2, 31) },
];
}