Lattice Grid Buy a licence

demo D273

A project plan, joined split view in Angular

The task tree and the timeline as one row-aligned surface: summary bars, progress rings, the people on each task, dependency arrows, real calendar dates, weekends shaded, and a baseline drawn behind each bar

gantt.mountSplit

This shows the task tree and the timeline as one row-aligned surface: summary bars, progress rings, the people on each task, dependency arrows, real calendar dates with weekends shaded, and a baseline behind each bar. It lets the names and the bars line up exactly, so a plan reads as one picture.

Building…
Loading a live grid…

This is the Angular version. A standalone component takes the whole configuration through one config input, surfaces grid events as outputs, and exposes the live grid on a getter for anything the inputs do not cover.

The configuration

import { Component, ViewChild, ElementRef, AfterViewInit, OnDestroy } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createGantt } from '@toclocoinc/lattice-grid/modules/gantt';

// mountSplit draws the task tree and the timeline as one aligned surface,
// beyond what the plain <lattice-gantt> component covers (that wraps the
// single-pane mount only), so this stays an imperative build against a
// ViewChild'd container, the same shape a non-Angular library would use.
@Component({
  selector: 'app-root',
  standalone: true,
  template: '<div #plan style="height:460px;overflow:auto"></div>',
})
export class AppComponent implements AfterViewInit, OnDestroy {
  @ViewChild('plan') planRef!: ElementRef<HTMLElement>;
  private gantt: any;

  ngAfterViewInit() {
    // A two-level work breakdown: leaves carry duration, percentComplete,
    // assignee, cost and actualCost; a summary parent is derived from its
    // children.
    const tasks: any[] = [/* the task tree */];
    const dependencies: any[] = [/* the dependency links */];
    this.gantt = createGantt({ tasks, dependencies, projectStart: '2027-01-04', calendar: 'weekends', autoSchedule: true });

    // Capture the agreed schedule as the baseline, so the view can draw the
    // planned ghost bar and the schedule can report variance against it.
    const baseline = new Map(this.gantt.captureBaseline().map((b: any) => [b.id, b]));
    this.gantt.setTasks(tasks.map((t) => {
      const b = baseline.get(t.id);
      return b ? { ...t, baselineStart: b.baselineStart, baselineEnd: b.baselineEnd } : { ...t };
    }));

    // evm turns on earned value against the status date; the SPI and CPI
    // columns read it, rolled up to the summaries and the project.
    this.gantt.mountSplit(this.planRef.nativeElement, {
      gridWidth: 420, rowHeight: 34, height: 440, zoom: 'day',
      nonWorking: 'weekends', showBaseline: true, showArrows: true, showProgress: true, evm: true,
      columns: [
        { key: 'name', title: 'Task', width: 0, kind: 'name' },
        { key: 'assignee', title: 'Owner', width: 96, kind: 'assignee' },
        { key: 'progress', title: '%', width: 44, kind: 'progress' },
        { key: 'spi', title: 'SPI', width: 58, kind: 'evm', metric: 'spi' },
        { key: 'cpi', title: 'CPI', width: 58, kind: 'evm', metric: 'cpi' },
      ],
    });
  }

  ngOnDestroy() { this.gantt?.destroy(); }
}

bootstrapApplication(AppComponent);

The task list and the timeline, as one aligned surface

A plan reads best when the names and the bars line up. Here they are one surface: the task tree on the left and the timeline on the right, laid out from the same row heights so every task sits on exactly the line its bar does, however a name wraps or a row grows. Scroll and the two move together, because they are not two views trying to keep in step, they are one.

The left side is a real work breakdown. Tasks nest under summary rows that roll up from their children, so an epic shows the span of everything inside it and collapses to hide the detail when you want the shape rather than the parts. Each row carries the person on the task and how far along it is, drawn as a ring that fills with progress. On the right, each bar sits on a calendar axis of real dates, milestones stand as diamonds, and the dependency links are drawn as arrows from one bar to the next so the order of the work is visible, not implied.

The schedule keeps working time. Point it at a working-day calendar and weekends are shaded and stepped over, so a five-day task that starts on a Thursday finishes the following Wednesday rather than running through the weekend. Capture a baseline and the plan you agreed is drawn as a faint bar behind each task, so a slip reads as the gap between where a task was meant to be and where it now is, task by task and rolled up to the summaries above.

The split view also earns its keep as a progress report. Alongside the names and dates it can show earned-value columns, so a reader sees not just where a task sits but whether it is ahead or behind and over or under spend. Each task carries how much of the plan was due by the status date, how much has actually been earned, and what it has cost, rolled up to the summaries and the whole project. Two ratios read at a glance: a schedule figure above one means ahead of plan and below one means behind, and a cost figure above one means under budget and below one means over. Point the plan at a status date and the columns settle the “are we on track” conversation with the same numbers a project office already uses.

How do I show earned value and schedule and cost performance?

Add earned-value columns to the split view with kind: 'evm', and give each task an actualCost (its budget falls back to the task’s cost, or its duration when no cost is set). The view surfaces planned value, earned value and actual cost, the schedule and cost variances between them, and the schedule and cost performance ratios (SPI and CPI), per task and rolled up to the summaries and the project. Call gantt.earnedValue({ statusDate }) when you want the same figures without the columns. A schedule ratio above one is ahead of plan; a cost ratio above one is under budget.

How do I draw a plan as a joined split view?

Load the gantt module and call createGantt({ tasks, dependencies, projectStart, calendar }), then mountSplit(element, options) to draw the joined surface. Give projectStart a real date and the axis reads as calendar days; set calendar to 'weekends' and the schedule keeps working time. Each task is a row with a duration; name one task as another’s parent to make a summary, and mark a task as a milestone for a diamond. Put a person on assignee and a percentComplete on each task for the avatars and progress rings. Call captureBaseline() once on the agreed plan, feed the result back as each task’s baselineStart and baselineEnd, and turn on showBaseline to draw the planned ghost bar. showArrows draws the dependency links and today marks the current date down the timeline.