Methods
Everything you can call on a chart after it is created: change the data, the options and the view, scroll, select, run an edit session, undo, and read the state back. This page gives each method’s signature, parameters, return value and, for the methods that can refuse, every result status with the payload it carries. The methods that cause events say which ones.
The methods are the members of the Gantt object that
createGantt returns. Most are the members of the headless
GanttCore as they are (it is gantt.core); the
last sections say what differs, and
Methods in React says where to find them there.
Conventions
Section titled “Conventions”- No
this. Every member is a plain function, soconst { getTask, select } = ganttworks. Properties (canUndo,canRedo,edit.isOpen,edit.isDirty) are getters: read them when you need them, because destructuring copies the value once. - Results, not exceptions. A method that can fail returns a result with a
status, so you can narrow it withswitch (result.status). Bad input is an issue or arejectedresult; it is never thrown. - What can throw.
createGanttthrows aTypeErrorwhen its container is not an element (a selector string,nullfrom aquerySelectorthat found nothing). Nothing else throws on bad input. A method rethrows what your own event handlers threw, once its work is done, when noerrorlistener is registered (see When a handler throws): a synchronous method throws, a promise rejects. - Sync and async.
updateTask,addLinkandremoveLinkreturn a promise. Everything else is synchronous and has finished when it returns. The promise methods apply their change inside the call too, unless a before-hook returns a promise; they are asynchronous because a hook may be. Await them to get the result. - Two statuses are shared.
busyandcancelledmean the same wherever they appear. - After
destroy(). The commands do nothing (the ones with a result resolve{ status: 'cancelled', reason: 'disposed' }), no event fires, and the queries keep answering from the state the chart had, so a clean-up can still read what the user left behind.
| Status | Shape | Meaning |
|---|---|---|
busy |
{ status: 'busy' } |
Another change waits for a before-hook’s answer, or (not for edit.*) controlled data holds a committed change that you have not applied yet, until your next setTasks. Nothing was changed. Try again later. |
cancelled |
{ status: 'cancelled', reason, error? } |
reason: 'hook': a before-hook answered false. 'error': a hook threw, rejected or answered something invalid, and error holds what it threw. 'disposed': the chart was destroyed. |
Results at a glance
Section titled “Results at a glance”| Method | Statuses it can report |
|---|---|
updateTask |
committed, unchanged, rejected (unknown-task, invalid-value), cancelled (hook, error, disposed), busy |
nudge |
committed, unchanged, refused (permission, unknown-task), cancelled (hook, error, disposed), busy |
addLink |
created, rejected (invalid, unknown-task, duplicate, cycle), cancelled (hook, error, disposed), busy |
removeLink |
deleted, rejected (unknown-link), cancelled (disposed), busy |
edit.begin() |
begun, already-open, busy, cancelled (disposed) |
edit.commit() |
committed, no-session, busy, cancelled (disposed) |
edit.rollback() |
rolled-back, no-session, busy, cancelled (disposed) |
undo(), redo() |
undone or redone, empty, stale, busy, cancelled (disposed) |
All methods
Section titled “All methods”| Group | Method | Returns |
|---|---|---|
| Lifecycle | destroy() |
void |
| Options | setOptions(patch) |
readonly Issue[] |
| Data | setTasks(tasks), setLinks(links) |
void |
updateTask(id, update) |
Promise<UpdateTaskResult> |
|
nudge(id, gesture, direction) |
Promise<NudgeResult> |
|
addLink(link) |
Promise<LinkCreateResult> |
|
removeLink(id) |
Promise<LinkDeleteResult> |
|
| View | setViewMode(id, options?) |
void |
zoomTo(pxPerDay, options?) |
void |
|
| Scrolling | scrollToDate(date, options?) |
void |
scrollToToday(options?) |
void |
|
scrollToTask(id, options?) |
void |
|
| Rows | expand(id), collapse(id), expandAll(), collapseAll() |
void |
| Selection | select(ids), selectLink(id), clearSelection() |
void |
| Edit session | edit.begin() |
SessionBeginResult |
edit.commit() |
SessionCommitResult |
|
edit.rollback() |
SessionRollbackResult |
|
edit.getChanges() |
SessionChanges | undefined |
|
edit.isOpen, edit.isDirty |
boolean (getters) |
|
| History | undo(), redo() |
HistoryResult |
canUndo, canRedo |
boolean (getters) |
|
| Reading | getTask(id) |
TaskRecord | undefined |
getLink(id) |
LinkRecord | undefined |
|
getVisibleRange() |
{ start: Date; end: Date } |
|
getRowMetrics(id) |
RowMetrics | undefined |
|
getBarRect(id) |
BarGeometry | undefined |
|
getRows() |
readonly GanttRow[] |
|
getRowWindow() |
{ firstRow; lastRow } |
|
getLayoutMetrics() |
LayoutMetrics |
|
getAppearance() |
GanttAppearance |
|
getIssues() |
{ data: Issue[]; options: Issue[] } |
|
| Events | on(name, handler) |
() => void (unsubscribe) |
| Popovers | hideTooltip() |
void |
| Escape hatch | core |
GanttCore |
Lifecycle and options
Section titled “Lifecycle and options”destroy
Section titled “destroy”destroy(): voidRemoves everything the chart added to its container (nodes, listeners, observers, the timer), then destroys its core. It is safe to call twice. Afterwards nothing fires and the commands do nothing. Call it when your page is done with the chart; in React the component does it for you.
setOptions
Section titled “setOptions”setOptions(patch: GanttOptionsPatch<TData>): readonly Issue[]Changes options without recreating the chart. patch takes any option of createGantt except
renderer, which is read once at creation. The full list is in Options.
- A key left out keeps its value. A key set to
undefinedgoes back to its default. - The groups
calendar,dates,scheduling,hierarchy,layout,timeline,scroll,interaction,displayandstringsmerge field by field with what was set before, one level deep: a field that is itself an object or a list (scheduling.skipNonWorkingDays,scheduling.snap,timeline.range,timeline.infinite,scroll.autoScroll,interaction.handles,calendar.holidays) is replaced whole, so the keys it leaves out are back to their defaults.tooltip,linkMenu,highlightTooltip,editing,historyandviewModesas objects, and every list (tasks,links,highlights, …) are replaced whole too: a field you leave out is back to its default.setOptions({ tasks: undefined })is aninvalid-optionissue, not a reset (taskshas no default); pass[]to empty the chart. - Each key goes only to what depends on it. Swapping an
on<Event>handler is O(1) and redraws nothing. - Returns the issues of the options now in use, the core’s and the renderer’s in one list. It is
the same list as
getIssues().options. A value that cannot be used is an issue and the default stays in effect; nothing throws.
gantt.setOptions({ theme: 'dark', locale: 'uk' }); // renderer and core options in one callgantt.setOptions({ scheduling: { propagation: undefined } }); // one field back to its defaultgantt.setOptions({ editing: false }); // a read-only chart
const issues = gantt.setOptions({ viewMode: 'fortnight' }); // no such modeconsole.log(issues.map((issue) => issue.code)); // ['unknown-view-mode']; the chart uses 'day'Changing the view mode this way (setOptions({ viewMode: 'week' })) fires
viewModeChange, exactly as setViewMode does.
setTasks and setLinks
Section titled “setTasks and setLinks”setTasks(tasks: readonly Task<TData>[]): voidsetLinks(links: readonly Link[] | undefined): voidReplace the tasks or the links. The chart diffs the new list by id and recomputes only what changed. They never mutate what you pass.
- With uncontrolled data (the default) the new tasks replace the chart’s own, and the undo history is cleared: its entries describe data that is gone.
- With controlled data this is how you answer a change: pass the tasks with the change applied
(
patchTaskswrites a change set into tasks of any shape), or a new array without it to reject the change. A new array always ends the draft the chart was showing, even with the same content. The chart isbusyuntil you do. - An open edit session keeps its base; the new tasks are diffed against it.
They fire no change events. They can fire rangeChange, issues, selectionChange (when a
selected task left the data) and historyChange. See
Data ownership and the
controlled data example.
updateTask
Section titled “updateTask”updateTask(id: TaskId, update: TaskUpdate): Promise<UpdateTaskResult>Changes a task’s dates or progress from code. update is { start?, end?, progress? }; dates take
a Date, an ISO string or epoch milliseconds.
| You pass | What happens |
|---|---|
start alone |
The task moves there and keeps its length, like a bar drag |
end alone |
The end edge moves and the start stays, like an end-edge drag |
start and end |
Both edges are set |
progress |
The progress, a number from 0 to 100 |
The update behaves like the drag it stands for:
- It propagates through links and children (
scheduling.propagation,hierarchy.moveChildren), so one call can move several tasks. All of them are in the oneChangeSetof thetasksChangeevent, whosereasonis'api'. - It runs
onBeforeTaskChange, which seesreason: 'api'and may pass, cancel or rewrite it. - Undo restores it like any other action.
- Values are exact: no snapping to the view mode and no rolling onto working days. A value off
scheduling.granularityis aligned asscheduling.alignInputsays and reported as amisalignedissue (withalignInput: 'reject'the update is refused). A day-precision task stays on whole days unlessscheduling.keepDayPrecisionisfalse. endis read as the task’s ownendis: a date-only value is the last day, inclusive, unless the task hasprecision: 'time'. It must come after the start (a milestone may end at its start).editing: falsedoes not stop it: that option is about the UI. Alockedtask is the one thing it leaves alone.
status |
Other fields | When |
|---|---|---|
'committed' |
changeSet: ChangeSet, issues: readonly ChangeSetIssue[] |
The change was applied and tasksChange fired. changeSet is the final one (after hooks and alignment). issues are the corrections made, such as an aligned date |
'unchanged' |
issues |
There was nothing to change: update has no fields, the values equal the current ones, or a hook or re-validation left nothing. A locked task is reported here, with a locked-task issue |
'rejected' |
reason: 'unknown-task' | 'invalid-value', issues |
Refused before a change was computed. 'unknown-task': no such id, or the task has no bar (a placeholder, an invalid task); issues is empty. 'invalid-value': an unreadable value, an end that is not after the start, a misaligned value with alignInput: 'reject', or update is not an object; issues says which |
'cancelled' |
reason: 'hook' | 'error' | 'disposed', error? |
A before-hook answered false ('hook'), threw, rejected or answered something invalid ('error', with error), or the chart was destroyed ('disposed') |
'busy' |
none | The chart is busy (see the table above). The update is neither read nor reported |
For 'invalid-value' the issues are also emitted as an issues event with source: 'change-set'.
const result = await gantt.updateTask('design', { start: '2026-03-09', end: '2026-03-13' });
switch (result.status) { case 'committed': console.log(`${result.changeSet.changes.length} tasks moved`); console.log(gantt.getTask('design')?.start); // the new date (uncontrolled data) break; case 'unchanged': console.log( 'Nothing to do', result.issues.map((issue) => issue.code), ); break; case 'rejected': console.warn(result.reason, result.issues); // 'unknown-task' or 'invalid-value' break; case 'cancelled': console.warn('A hook said no, or the chart is gone:', result.reason); break; case 'busy': console.warn('Another change is waiting; try again later'); break;}With controlled data, a 'committed' update leaves the chart busy until your next setTasks; the
next updateTask before that resolves 'busy'. See the
programmatic updates example.
nudge(id: TaskId, gesture: 'move' | 'resize-start' | 'resize-end', direction: -1 | 1): Promise<NudgeResult>Moves a task, or one of its edges, by one snap step: what an arrow key does with
keyboard.edit, for buttons of your own. The edge goes to the next boundary
of the step of that gesture (scheduling.snap, else the view mode’s, else the data step) in
direction (-1 earlier, 1 later), and nothing else differs from dragging it there: snapping,
skipped non-working days, the minimum length of one step, the propagation through links and
children, the before-hooks and the permissions (editing, editable, locked, fixedDuration).
Where updateTask takes exact values from code and has the reason 'api', a nudge is a UI action:
its ChangeSet has the reason 'move', 'resize-start' or 'resize-end', and a refusal is
emitted as editRefused.
The result is the commit’s (committed, unchanged when the edge cannot move that way,
cancelled, busy), or { status: 'refused', reason: 'permission' | 'unknown-task' }.
The call says nothing and does not scroll: the live region and the scroll to the bar belong to the
arrow keys of keyboard.edit. For buttons of your own, tell the user what happened from the result,
or from onTasksChange. A before-hook that throws is in the result (cancelled, error), as it is for
updateTask, and is not reported a second time. A direction other than 1 or -1, or a gesture other
than the three, changes nothing (unchanged).
addLink
Section titled “addLink”addLink(link: Link): Promise<LinkCreateResult>Creates a link from code. link is { source, target, type?, lag?, id?, editable? }. The chart
checks the link, runs onBeforeLinkCreate and appends it (uncontrolled data) or leaves it to you
(controlled data), then fires linkCreate. Permissions such as linkable apply to the user’s
gestures, not to this call.
status |
Other fields | When |
|---|---|---|
'created' |
id: string, link: Link |
The link was created. link is the one written: yours, or a copy with a free id when its default id was taken |
'rejected' |
reason, and issues for 'invalid' |
Refused; nothing was changed or emitted. The reasons are below |
'cancelled' |
reason: 'hook' | 'error' | 'disposed', error? |
onBeforeLinkCreate answered false, or threw, rejected or answered something invalid, or the chart was destroyed |
'busy' |
none | The chart is busy |
The reason of a rejection:
reason |
The link was refused because |
|---|---|
'invalid' |
The model would drop it: a malformed field (an unknown type), a lag that is invalid or has months, or a misaligned lag with alignInput: 'reject'. issues lists the problems with their path, such as link.lag |
'unknown-task' |
The source or the target is not a task (also when the argument is not an object) |
'duplicate' |
A link with the same source, target and type exists, whatever its id, or the explicit id is in use |
'cycle' |
It would close a cycle of links, or links a task to itself |
The first three are decided without asking the hooks. A cycle is rejected whatever the hooks answer:
onBeforeLinkCreate is told through wouldCreateCycle so it can explain.
const result = await gantt.addLink({ source: 'design', target: 'build', type: 'SS', lag: 1 });
if (result.status === 'created') { console.log('Created', result.id); // 'design:SS:build'} else if (result.status === 'rejected') { console.warn(result.reason); // 'invalid', 'unknown-task', 'duplicate' or 'cycle'}removeLink
Section titled “removeLink”removeLink(id: string): Promise<LinkDeleteResult>Deletes a link by its id (LinkRecord.id, as getLink and the events give it). Uncontrolled data
loses the link: it is removed from links, or, for a link that came from Task.dependencies, the
entry is removed from its target task. Controlled data stays yours to change. Then linkDelete
fires. No hook is asked and editable: false does not stop the call.
status |
Other fields | When |
|---|---|---|
'deleted' |
id: string, link: LinkRecord |
The link was deleted. link is the record it had |
'rejected' |
reason: 'unknown-link' |
No link has that id |
'cancelled' |
reason: 'disposed' |
The chart was destroyed (the only way removeLink is cancelled) |
'busy' |
none | The chart is busy |
See Links and types and the link editing example.
View and scrolling
Section titled “View and scrolling”On the vanilla Gantt these methods return nothing: the chart scrolls itself in its next frame and
there is nothing for you to apply. On GanttCore they return the ScrollRequest
a renderer performs. The tables below describe the options and when the core returns undefined.
setViewMode
Section titled “setViewMode”setViewMode(id: string, options?: SetViewModeOptions): voidSwitches the view mode: 'hour', 'quarter-day', 'half-day', 'day', 'week', 'month',
'year', or an id of your own from viewModes.
| Option | Type | Default | Meaning |
|---|---|---|---|
keepScroll |
boolean |
true |
true: the date at the centre of the viewport stays in place. false: scroll as scroll.initial says, as at creation |
An unknown id falls back to 'day' with an unknown-view-mode issue. A non-string id does
nothing. Fires viewModeChange, and rangeChange with 'view-mode' when the range changes. Any
zoom set by zoomTo is dropped.
gantt.setViewMode('week'); // the centre date stays putgantt.setViewMode('month', { keepScroll: false }); // as at creation: scroll.initial decideszoomTo
Section titled “zoomTo”zoomTo(pxPerDay: number, options?: ZoomToOptions): voidSets the zoom level of the current view mode, in pixels per day. The view mode stays the same.
| Option | Type | Default | Meaning |
|---|---|---|---|
anchor |
number |
the centre | The x of the date that stays in place, in pixels from the viewport’s left edge |
A value that is not a finite number above zero does nothing. Another view mode drops the zoom.
Fires rangeChange with 'zoom' when the range changes.
const { pxPerDay } = gantt.core.getSnapshot().timeline.scale;gantt.zoomTo(pxPerDay * 1.5); // zoom in, keeping the centre dategantt.zoomTo(pxPerDay / 2, { anchor: 0 }); // zoom out, keeping the left-edge datescrollToDate and scrollToToday
Section titled “scrollToDate and scrollToToday”scrollToDate(date: DateInput, options?: ScrollToDateOptions): voidscrollToToday(options?: ScrollToDateOptions): voidScroll a date, or today, into view. With the infinite timeline the range grows first.
| Option | Type | Default | Meaning |
|---|---|---|---|
align |
number | 'start' | 'center' | 'end' |
scroll.align ('start') |
Where the date lands in the viewport. A number from 0 (left edge) to 1 (right edge); 'start', 'center' and 'end' are 0, 0.5 and 1. Anything else uses the default |
behavior |
'smooth' | 'instant' | 'auto' |
scroll.behavior ('smooth') |
How to scroll. 'auto' leaves it to the browser (the scroller’s CSS scroll-behavior) |
gantt.scrollToDate('2026-03-16'); // smooth, the date at the left edgegantt.scrollToDate(new Date(2026, 2, 16), { align: 'center', behavior: 'instant' });gantt.scrollToToday({ align: 0.25 }); // today a quarter of the way inAn unreadable date does nothing. A call made before the chart has a width (it is created in a
hidden container, say) is not lost: the latest one is kept and performed at the first width, in
place of scroll.initial, which a later change of the option cannot override.
scrollToTask
Section titled “scrollToTask”scrollToTask(id: TaskId, options?: ScrollToTaskOptions): voidScrolls a task’s row and bar into view, on both axes. It takes the options of scrollToDate and:
| Option | Type | Default | Meaning |
|---|---|---|---|
align |
number | 'start' | 'center' | 'end' | 'nearest' | false |
scroll.align |
Where the bar lands sideways |
alignY |
The same | align |
Where the row lands vertically |
expand |
boolean |
true |
Expand the collapsed ancestors of a hidden row first (fires expandedChange) |
'nearest' is the least scroll that shows it, and none when it is in view. false leaves that axis
alone. An unknown id scrolls nowhere, and neither does a task whose row is hidden by a collapsed
parent when expand is false. A task without a bar (a placeholder) scrolls vertically only.
gantt.scrollToTask('build'); // smooth, aligned at the startgantt.scrollToTask('build', { align: 'center', alignY: 'center', behavior: 'instant' });gantt.scrollToTask('qa', { expand: false }); // nothing if its parent is collapsedgantt.scrollToTask('qa', { align: 'center', alignY: false }); // sideways onlygantt.scrollToTask('qa', { align: 'nearest' }); // nothing if it is in viewscrollTo
Section titled “scrollTo”scrollTo(position: { left?: number; top?: number; behavior?: ScrollBehaviorOption }): voidScrolls to a position on either axis or both. An axis left out stays where it is, and both are
clamped to the content. top is in body pixels: the first row is at 0, right below the header.
gantt.scrollTo({ top: 0 }); // back to the first row, sideways staysgantt.scrollTo({ left: 400, top: 120, behavior: 'smooth' });Scrolling and view methods fire scroll and, with an infinite timeline, rangeChange. See the
scrolling example and the
view modes example.
Scroll requests
Section titled “Scroll requests”On GanttCore, setViewMode, zoomTo and the scrollTo* methods return a ScrollRequest, or
undefined when there is nothing to scroll: the chart is destroyed, the input is unusable (an
unreadable date, a non-string view mode id, a zoom that is not above zero), the request would not move
anything (the same view mode or zoom again), the task has no visible row, or the chart has no width
yet. A request is also kept in the snapshot as scrollRequest, so a renderer sees the requests of
calls it did not make.
| Field | Type | Meaning |
|---|---|---|
id |
number |
Grows by one with every request of the chart. Apply each id once |
reason |
'initial' | 'scroll-to' | 'view-mode' | 'zoom' | 'data' | 'options' | 'resize' | 'drag' |
Why the core asks for the scroll |
from |
number | undefined |
A smooth request: jump here instantly first, so the animation starts without a jump |
left |
number | undefined |
The horizontal offset to scroll to, in content pixels; undefined keeps it |
top |
number | undefined |
The vertical offset to scroll to, in body pixels; undefined keeps it |
behavior |
'smooth' | 'instant' | 'auto' |
How to scroll. A scrollTo* whose range change cut the current view away is 'instant' |
The DOM renderer applies requests for you. You only meet them when you write your own renderer; see Custom renderers.
Rows and selection
Section titled “Rows and selection”expand(id: TaskId): voidcollapse(id: TaskId): voidexpandAll(): voidcollapseAll(): voidtoggle(id) opens a closed parent and closes an open one.
Open or close the parents of the task tree. expandAll expands every parent that exists now;
parents that appear later start collapsed. Each fires expandedChange when the expanded set
changes, and does nothing when it does not. setOptions({ hierarchy: { expandedKeys } }) sets the
state outright and fires nothing. See Hierarchy.
Selection
Section titled “Selection”select(ids: readonly TaskId[]): voidselectLink(id: string | undefined): voidclearSelection(): voidselectreplaces the selected tasks. Ids that are not tasks are left out, duplicates are dropped, the order you gave is kept, and the selected link is kept.selectLinkselects a link, or none withundefined. An unknown id selects none. The selected tasks are kept.clearSelectionclears both.
Each fires selectionChange when the selection changes. By default the chart never selects a task
by itself: a click on a bar only fires taskClick. With
selection.onClick it selects that task. A click on a link selects it.
Hover and focus of a row
Section titled “Hover and focus of a row”setHoveredRow(id: TaskId | undefined): voidfocusTask(id: TaskId | undefined): voidfocusBar(id: TaskId): voidFor what stands beside the chart. setHoveredRow lights the row of a task (undefined gives the
lit row back to the pointer); the row gets data-hovered, and onRowHoverChange fires. It shows
only where the row hover is on: see the row hover. focusTask says
which task’s row or bar has the keyboard focus. A bar that has the browser’s focus hands it to the
bar of that task, a task list to its row, and otherwise a list takes it; nothing
moves it onto a bar from elsewhere. A task without a visible row is ignored, and one whose row goes
away loses the focus. focusTask(undefined) does not take the browser’s focus away, so a bar that
has it is the focused task again at the next draw.
focusBar moves the browser’s focus to a bar from anywhere and scrolls to it, with
keyboard.barFocus on. It does nothing for a task without a bar in a visible
row, or when the option is off.
gantt.select(['design', 'build']);gantt.selectLink('design:FS:build');gantt.core.getSnapshot().selection; // { tasks: ['design', 'build'], link: 'design:FS:build' }gantt.clearSelection();Edit session
Section titled “Edit session”edit.begin(): SessionBeginResultedit.commit(): SessionCommitResultedit.rollback(): SessionRollbackResultedit.getChanges(): SessionChanges | undefinededit.isOpen: booleanedit.isDirty: booleanThe “Edit, Save, Cancel” flow. All three commands are synchronous. A session does not hold changes
back: they are applied and reported as they happen. begin remembers the data, getChanges is the
net difference from it (a task changed and changed back is not listed), commit closes the session
and returns that difference, and rollback puts the remembered data back. At most one session is
open. Concepts: Edit sessions and history.
edit.begin():
status |
Other fields | When |
|---|---|---|
'begun' |
none | A session opened on the current data |
'already-open' |
none | One is open already. Sessions do not nest; it keeps its base |
'busy' |
none | A before-hook is being awaited |
'cancelled' |
reason: 'disposed' |
The chart was destroyed |
edit.commit():
status |
Other fields | When |
|---|---|---|
'committed' |
changes: SessionChanges |
The session closed and its changes were kept. changes.reason is 'session'; changes.links lists the links added and removed |
'no-session' |
none | No session is open |
'busy' |
none | A before-hook is being awaited |
'cancelled' |
reason: 'disposed' |
The chart was destroyed |
edit.rollback():
status |
Other fields | When |
|---|---|---|
'rolled-back' |
changes: SessionChanges |
The session closed and the data returned to its base. changes.reason is 'rollback'; the same object is the payload of editRollback |
'no-session' |
none | No session is open |
'busy' |
none | A before-hook is being awaited |
'cancelled' |
reason: 'disposed' |
The chart was destroyed |
rollback also cancels a drag in progress first, never asks a before-hook, and puts the undo
history back to where it was at begin.
gantt.edit.begin(); // { status: 'begun' }// ... the user drags bars; getTask() shows each change, tasksChange fires ...
if (gantt.edit.isDirty) { const saved = gantt.edit.commit(); if (saved.status === 'committed') await post(saved.changes); // the net difference}
// or, to cancel:const cancelled = gantt.edit.rollback();if (cancelled.status === 'rolled-back') console.log(cancelled.changes.changes.length, 'reverted');With controlled data the rollback is yours to apply: the editRollback event (or the changes of
the result) carries what to revert. The edit session example is the
whole flow with Save, Cancel and a server that can refuse.
getChanges() returns what commit() would return as changes (a SessionChanges) at this
moment, or undefined when no session is open. isOpen says whether a session is open, and
isDirty whether it is open and has changes.
History
Section titled “History”undo(): HistoryResultredo(): HistoryResultcanUndo: booleancanRedo: booleanStep back and forward through the actions the chart recorded: one entry per drag, updateTask
call, link created and link deleted. It needs history: true (or { limit }, default 100); the
history is off by default, and without it canUndo and canRedo stay false and undo() returns
{ status: 'empty' }. A drag in progress is cancelled first. Steps are synchronous, work inside an
edit session, and never ask the before-hooks. A new action clears what could be redone.
An undo or redo fires the usual events: tasksChange with reason: 'undo' or 'redo' and its
per-task events, or linkCreate or linkDelete, and historyChange.
status |
Other fields | When |
|---|---|---|
'undone' or 'redone' |
entry: HistoryEntry |
The step was applied. entry is the entry as it is now on the other stack |
'empty' |
none | Nothing to undo or redo |
'stale' |
entry |
The data no longer shows the entry’s result (it was changed behind the history’s back), so the step was not applied and the history was cleared |
'busy' |
none | A before-hook is being awaited, or controlled data holds a change you have not applied |
'cancelled' |
reason: 'disposed' |
The chart was destroyed |
A HistoryEntry is { type: 'tasks', changeSet }, { type: 'linkCreate', id, link } or
{ type: 'linkDelete', id, link, restore }.
const gantt = createGantt(container, { tasks, history: { limit: 50 } });
undoButton.onclick = () => { const result = gantt.undo(); if (result.status === 'stale') alert('The plan changed elsewhere; the history was reset.');};gantt.on('historyChange', ({ canUndo, canRedo }) => { undoButton.disabled = !canUndo; redoButton.disabled = !canRedo;});See the undo and redo example.
Reading the state
Section titled “Reading the state”All of these are synchronous queries. They still answer after destroy().
| Method | Returns |
|---|---|
getTask(id) |
The task’s TaskRecord: dates as Dates, lengths, progress, place in the tree, issues, conflicts. undefined for an unknown id. Frozen, and the same object while nothing it shows has changed |
getLink(id) |
The link’s LinkRecord (source, target, type, lag, active, inCycle, …). undefined for an unknown id |
getVisibleRange() |
{ start, end }: the dates at the viewport’s left and right edges |
getRowMetrics(id) |
{ id, index, y, height } of the row, y in body pixels (0 is the top of the first row, below the header). undefined when the id is unknown or the row is hidden |
getBarRect(id) |
{ id, rowIndex, kind, x, y, width, height, radius, progressWidth } of the bar as drawn, a drag in progress included. x is in content pixels, y in body pixels. undefined without a bar |
getRows() |
The visible rows in order, each a GanttRow. The same array until a row changes, so comparing it says whether to draw again. O(rows) after such a change, about 15 ms for 10 000 tasks, and only when it is read |
getRowWindow() |
{ firstRow, lastRow }: the rows the chart draws now. With none, firstRow > lastRow |
getLayoutMetrics() |
{ headerHeight, rowHeight, barHeight, bodyHeight, contentWidth, viewportWidth, viewportHeight }, the same object while they are the same |
getAppearance() |
{ theme, height, rowStripes, rowHover }: what the renderer draws that the core does not know. on('appearanceChange', fn) hears a change. Not on gantt.core |
getIssues() |
{ data, options }: the current problems of the data and of the options (the renderer’s options included). The same lists the issues event reports |
Anything else lives in the snapshot, gantt.core.getSnapshot(). To export the data, read the
tasks and links the chart holds; uncontrolled data includes every change applied so far:
const { state } = gantt.core.getSnapshot();
// The data as the chart has it: your own Task and Link objects, patched by the chart's changes.const saved = { tasks: state.tasks, links: state.links };
// Computed records, in row order (collapsed rows are left out) ...const rows = state.derived.rows.map((row) => gantt.getTask(row.id));// ... or for every task.const all = state.derived.model.tasks.map((task) => gantt.getTask(task.id));Inside an event handler the snapshot can be one step behind the event, see When events are delivered.
Events and popovers
Section titled “Events and popovers”on<K extends GanttEventName>(name: K, handler: GanttEventMap<TData>[K]): () => voidAdds a listener for an event, with the same arguments as the on<Event> option, and returns a
function that removes it. There is no off and no once. Everything about the events, their
arguments and their order is on Events.
hideTooltip
Section titled “hideTooltip”hideTooltip(): voidCloses the popover that is open: a task tooltip, the link menu or a day label. Popovers open from
the pointer; there is no method to open one. Only Gantt has it.
readonly core: GanttCore<TData>The chart’s core, for what the vanilla object leaves out: the snapshot and its subscription, the scroll requests and the input methods. See below.
What Gantt and GanttCore do not share
Section titled “What Gantt and GanttCore do not share”Gantt is GanttCore with a renderer on it, so most members are the same functions. These differ:
| Member | On Gantt |
On GanttCore |
|---|---|---|
setOptions |
Takes the renderer’s options too (theme, strings, display, tooltip, …) |
Core options only |
setViewMode, zoomTo, scrollTo* |
Return void; the renderer performs the scroll |
Return ScrollRequest | undefined |
getIssues, on('issues'), setOptions result |
Include the renderer’s option issues | The core’s only |
destroy |
Removes the DOM, then destroys the core | Destroys the core |
hideTooltip, core |
Yes | No |
setViewport, scroll, send, refreshNow |
No: use gantt.core.… |
Yes |
getSnapshot, subscribe |
No: use gantt.core.… |
Yes |
The members only the core has are for renderers and for state bindings. They are synchronous and,
after they changed something, never throw because one of your listeners did: that error goes to the
error listeners, or to the unhandled-rejection channel, so the result a renderer needs is not lost.
| Method | What it does | Returns |
|---|---|---|
setViewport({ width, height }) |
The renderer reports the scroller’s size, in CSS pixels. The first non-zero width mounts the timeline and applies scroll.initial |
ScrollRequest | undefined |
scroll({ left, top }) |
The renderer reports its scroll position, once per frame. Runs the infinite timeline’s policy and re-places a dragged bar | The ViewportChange to apply in the same frame (scrollLeft += change.shift), or undefined |
send(event) |
The renderer’s input adapter sends a pointer, key, cancel or timeline event in content coordinates. The core applies previews, commits and clicks and emits the events | readonly RendererEffect[]: the auto-scroll velocity, link preview and feedback left to the renderer |
refreshNow() |
Reads “now” again; publishes a new snapshot only when the minute changed. Call it from one timer aligned to the next minute | void |
getSnapshot() |
The renderer’s view-model: state, view mode, timeline, layout, selection, feedback, busy, scrollRequest, … The same object until something in it changed |
GanttSnapshot |
subscribe(listener) |
Calls listener after every change of the snapshot. With getSnapshot it is the contract of React’s useSyncExternalStore |
A function that removes it |
const off = gantt.core.subscribe(() => { const { busy, selection, viewMode } = gantt.core.getSnapshot(); statusBar.textContent = `${viewMode.id}, ${selection.tasks.length} selected${busy ? ', saving' : ''}`;});Custom renderers shows how a renderer uses them; React’s useGanttState
reads the snapshot for you.
Methods in React
Section titled “Methods in React”<Gantt> and useGantt expose the same methods; React runs the lifecycle for you.
| You have | It has |
|---|---|
The ref of <Gantt>: a GanttHandle |
Every member of Gantt except destroy, plus getSnapshot and subscribe. The ref is null after the component unmounts and while a hidden <Activity> has the chart |
The instance of useGantt: a GanttInstance |
The same, plus setContainer and slots, which <Gantt> uses |
const chart = useRef<GanttHandle>(null);
const goToBuild = () => chart.current?.scrollToTask('build');const save = async () => { const result = await chart.current?.updateTask('design', { progress: 100 }); if (result?.status === 'rejected') console.warn(result.reason);};Both objects are the same for the life of the component. Prefer props to methods for state the
component owns (tasks, viewMode, expandedKeys, selectedKeys, options): a prop that changes
later wins over a setOptions call. See React for the props, controlled and
uncontrolled values, and the ref handle, and the
ref handle example.
Related
Section titled “Related”- Events: what a chart reports, with every payload, and the before-hooks.
- Options: what
createGanttandsetOptionsaccept. - Edit sessions and history, Change sets and hooks and Data ownership: the model behind the results.
- Examples: basic chart, edit session, controlled data, undo and redo, programmatic updates and scrolling.
- API reference:
Gantt,GanttCore,UpdateTaskResult,LinkCreateResult,LinkDeleteResult,HistoryResult,ScrollRequestandcreateGantt.