demo D273
A project plan, joined split view
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
The configuration
'gantt-split-view': () => ({
rows: [],
config: {},
foot: [
'one joined surface: the task tree on the left and the timeline on the right, aligned row for row',
'summary rows roll up from their children, each bar carries a progress ring and the person on the task, and the dependency links are drawn as arrows',
'the axis runs on real calendar dates and the working-day calendar shades weekends, which the schedule steps over',
'a captured baseline draws the original plan as a ghost bar behind each task, so a slip reads as the gap between planned and actual',
],
mount: (el: HTMLElement, LG: any) => {
void LG;
const wrap = document.createElement('div');
wrap.style.cssText = 'border:1px solid var(--rule);border-radius:9px;background:var(--paper);min-width:0;overflow:auto';
el.append(wrap);
let gantt: any;
let view: any;
let disposed = false;
loadGantt()
.then((G: any) => {
if (disposed) return;
const { tasks, dependencies } = splitPlan();
const projectStart = '2027-01-04';
gantt = G.createGantt({ tasks, dependencies, projectStart, calendar: 'weekends', autoSchedule: true });
const baseline = new Map(
gantt.captureBaseline().map((b: any) => [b.id, b])
);
const slipped: Record<string, number> = { buildApi: 12, qa: 7 };
const actual = tasks.map((t) => {
const b: any = baseline.get(t.id);
const merged: any = { ...t };
if (b) { merged.baselineStart = b.baselineStart; merged.baselineEnd = b.baselineEnd; }
if (slipped[t.id] != null) merged.duration = slipped[t.id];
return merged;
});
gantt.setTasks(actual);
const today = G.toDayNumber ? G.toDayNumber('2027-01-22') : null;
view = gantt.mountSplit(wrap, {
gridWidth: 300,
rowHeight: 34,
height: 440,
zoom: 'day',
nonWorking: 'weekends',
showBaseline: true,
showArrows: true,
showProgress: true,
...(Number.isFinite(today) ? { today } : {}),
});
})
.catch((err) => console.error('[gantt-split]', err));
return () => {
disposed = true;
view?.destroy?.();
gantt?.destroy?.();
};
},
})
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.
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.