Skip to content

Groups and children

A task with parentId is a child; a task that has children is a group (a phase here). The rows are the tasks in pre-order, a group followed by its children, and each group is open or shut. This chart has three phases and the controls that decide what happens to their children: whether they move with the phase, and which edges of a child stay pinned to which edges of its phase.

Terminal window
pnpm add @ganttlane/dom # vanilla
pnpm add @ganttlane/react react react-dom # React
View on GitHub
data.ts
import type { Link, Task } from '@ganttlane/core';
// Three phases, each with children: `parentId` makes the tree, and the rows are the tasks in
// pre-order (a phase, then its children). 2026-03-02 is a Monday; a task with an `end` ends on
// that day (inclusive).
//
// A child is tied to its phase in two steps:
// `followParent: true` the child moves with its phase (when `hierarchy.moveChildren` lets it);
// `parentAnchor` which edges of the child are pinned to which edges of the phase:
// 'SS' start to start, 'FF' end to end, 'FS' start to the phase's end,
// 'SF' end to the phase's start, or two of them (one per edge).
// A child with no `parentAnchor` takes `hierarchy.childAnchor`.
export const tasks: Task[] = [
// ---- Plan: collapsed when the chart opens ----
{ id: 'plan', name: 'Plan', start: '2026-03-02', end: '2026-03-06' },
{
id: 'research',
name: 'Research',
parentId: 'plan',
start: '2026-03-02',
end: '2026-03-04',
parentAnchor: 'SS', // its start is pinned to the start of Plan
followParent: true,
},
{
id: 'spec',
name: 'Spec',
parentId: 'plan',
start: '2026-03-04',
end: '2026-03-06',
parentAnchor: 'FF', // its end is pinned to the end of Plan
followParent: true,
},
// ---- Build: expanded, and the one to drag ----
{ id: 'build', name: 'Build', start: '2026-03-09', end: '2026-03-20' },
{
id: 'backend',
name: 'Backend',
parentId: 'build',
start: '2026-03-09',
end: '2026-03-16',
parentAnchor: 'SS', // start pinned: it moves with Build, and a resize of Build's end leaves it
followParent: true,
},
{
id: 'frontend',
name: 'Frontend',
parentId: 'build',
start: '2026-03-09',
end: '2026-03-20',
parentAnchor: ['SS', 'FF'], // both edges pinned: it stretches with Build
followParent: true,
},
{
id: 'qa',
name: 'QA',
parentId: 'build',
start: '2026-03-16',
end: '2026-03-20',
followParent: true, // no `parentAnchor`: the pin is `hierarchy.childAnchor`
},
{
id: 'docs',
name: 'Docs',
parentId: 'build',
start: '2026-03-11',
end: '2026-03-18',
// Not followParent: only `moveChildren: 'all'` moves it, with the start of Build
},
// ---- Launch: collapsed when the chart opens ----
{ id: 'launch', name: 'Launch', start: '2026-03-23', end: '2026-03-27' },
{
id: 'marketing',
name: 'Marketing',
parentId: 'launch',
start: '2026-03-25',
end: '2026-03-27',
parentAnchor: 'FF',
followParent: true,
},
{
id: 'support',
name: 'Support',
parentId: 'launch',
start: '2026-03-26',
end: '2026-03-27',
followParent: true,
},
];
export const links: Link[] = [];

Files

  • data.ts: the three phases and their children, with followParent and parentAnchor.
  • options.ts: hierarchy (moveChildren, childAnchor, expandedKeys), the calendar and the row stripes.
  • helpers.ts: the values the selects offer, and sentences for the status line.
  • vanilla.ts and react.tsx: the chart, the buttons and the selects.

Options

Option Value What it does
hierarchy.moveChildren 'all' Which children move with their phase: 'all', 'following' or 'none' (the select)
hierarchy.childAnchor 'FF' The pin of a child with followParent and no parentAnchor (default 'SS'; the select)
hierarchy.expandedKeys ['build'] Which phases are open (React: defaultExpandedKeys); Plan and Launch start shut
task.followParent true The child moves with its phase, by its parentAnchor
task.parentAnchor 'SS', 'FF', ['SS', 'FF'] Which edges of the child are pinned to which edges of the phase
calendar.weekends [] Every day is a working day, so the conflicts you see are about the hierarchy
display.rowStripes 'group' Zebra stripes for each phase with its children
viewMode 'week' One column a week: the whole plan fits
onExpandedChange a function (in the example) Called with the open phases after expand, collapse, expandAll, collapseAll
onTasksChange a function (in the example) One ChangeSet per drag, with every child it moved

What to try

  • Drag Build with Move children on all. Backend, Frontend and QA follow by their pins and Docs follows with the start of Build. The status line lists what moved.
  • Change Move children to following and drag Build again: Docs stays, because it has no followParent. With none nothing follows, and the children that are left outside Build are drawn with a red outline: that is the outside-parent conflict, reported in the status line.
  • Drag the right edge of Build. Frontend (pinned at both edges) stretches with it, QA (pinned by its end, from childAnchor: 'FF') moves, and Backend (pinned by its start only) stays. Change Child anchor to SS and try again: QA now stays and follows the start instead.
  • Collapse Build and drag it. The children you cannot see move all the same; expand it again to check. Toggle Build, Expand all and Collapse all call expand, collapse, expandAll and collapseAll, and onExpandedChange reports the open phases.
  • Drag a child (say Backend) past the edge of Build: it is free to go, and the conflict says which edge it crossed.

Notes

  • Rows. A child follows its phase in the task list. While its phase is shut the child has no row and no bar (rowIndex of its record is undefined), and it still moves with the phase.
  • moveChildren. 'all' moves the children with followParent by their pin, and every other child with the start of its phase (it keeps its distance from it). 'following' moves only the children with followParent. 'none' moves no child; the children stay where they were, and may end up outside their phase.
  • Pins. 'SS' pins the start of the child to the start of its phase, 'FF' the end to the end, 'FS' the start to the end of the phase and 'SF' the end to its start. Give two (one per edge, ['SS', 'FF']) to pin both: the child then stretches with the phase. The distance a child had from its phase when the data was loaded is kept.
  • Conflicts are warnings. A child outside its phase is accepted data: it is flagged on the bar (data-conflict), listed in record.conflicts and in changeSet.conflicts, and passed to onBeforeTaskChange, which may refuse it (see A release plan).
  • React. The open phases are a prop of their own: expandedKeys is controlled, and defaultExpandedKeys is where an uncontrolled chart starts (the buttons then work through the ref). hierarchy takes only moveChildren and childAnchor, and is passed from state after the spread of options: in React the props are the truth, so an option changed with setOptions on the ref would be overwritten by the next render.