Skip to content

Data formats

A task’s dates, its length, its precision and its links can be written in more than one way. The chart reads all of them into one thing, a TaskRecord with real Dates, and this example writes the same kind of plan in each shape, side by side. Each bar is named after how it was written. Hover a bar for how the chart read it, or click it: the line under the chart shows the task as it is in data.ts (record.task, the object you gave, which the chart never changes) and the days or moments it covers. The checkbox flips dates.endInclusive, and the select zooms in far enough to read the times of day.

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';
/** What `task.data` holds: your own payload, which the library never reads. */
export interface Meta {
/** The part of the README the task belongs to. */
group: 'Start and end' | 'Duration' | 'Precision' | 'Links';
/** How the input is read, in a sentence: the tooltip shows it. */
note: string;
}
// 2026-03-16 is a Monday. The tasks of a group say the same thing in different ways, so their
// bars line up. Notes on the dates:
//
// - `new Date(2026, 2, 16)` is local midnight of 16 March (months count from 0). A number is read
// the same way, through the local clock, so it is made from a `Date` here: an epoch literal
// would land on another day in some time zones.
// - A value with a time of day (`'2026-03-23T09:00'`) makes a timed task. It must be a multiple
// of `scheduling.granularity`, so `options.ts` sets it to one hour.
export const tasks: Task<Meta>[] = [
// ---- start and end: the same five days, written four ways, and a timed task ----
{
id: 'date',
name: 'Date',
start: new Date(2026, 2, 16),
end: new Date(2026, 2, 20),
data: {
group: 'Start and end',
note: 'A Date at local midnight is a date-only value: whole days.',
},
},
{
id: 'iso-date',
name: 'ISO date',
start: '2026-03-16',
end: '2026-03-20',
data: {
group: 'Start and end',
note: 'A date-only string is a whole-day value.',
},
},
{
id: 'epoch',
name: 'Epoch ms',
start: new Date(2026, 2, 16).getTime(),
end: new Date(2026, 2, 20).getTime(),
data: {
group: 'Start and end',
note: 'A number is read through the local clock, like a Date.',
},
},
{
id: 'iso-time',
name: 'ISO date-time',
start: '2026-03-23T09:00',
end: '2026-03-24T17:00',
data: {
group: 'Start and end',
note: 'A time of day makes a timed task: its end is an exact moment.',
},
},
// ---- duration instead of an end ----
{
id: 'days',
name: 'duration 3',
start: '2026-03-16',
duration: 3,
data: { group: 'Duration', note: 'A number is days.' },
},
{
id: 'three-days',
name: "duration '3d'",
start: '2026-03-16',
duration: '3d',
data: { group: 'Duration', note: 'A string with a unit: the same three days.' },
},
{
id: 'week',
name: "duration '1w'",
start: '2026-03-18',
duration: '1w',
data: { group: 'Duration', note: 'A week is seven calendar days, the weekend included.' },
},
{
id: 'hours',
name: "duration '6h'",
start: '2026-03-23T09:00',
duration: '6h',
data: { group: 'Duration', note: 'Hours make a timed task, counted from its start.' },
},
// ---- precision: the same two dates, read as days or as moments ----
{
id: 'precision-day',
name: "precision 'day'",
start: '2026-03-23',
end: '2026-03-25',
precision: 'day',
data: { group: 'Precision', note: 'Whole days: the end is a day, whatever else it says.' },
},
{
id: 'precision-time',
name: "precision 'time'",
start: '2026-03-23',
end: '2026-03-25',
precision: 'time',
data: {
group: 'Precision',
note: 'Exact moments: the end is the midnight that starts 25 March.',
},
},
// ---- links: in `links`, and as `dependencies` of the target ----
{
id: 'links-a',
name: 'links[] source',
start: '2026-03-16',
end: '2026-03-17',
data: { group: 'Links', note: 'The predecessor of a link in the links array.' },
},
{
id: 'links-b',
name: 'links[] target',
start: '2026-03-19',
end: '2026-03-20',
data: {
group: 'Links',
note: 'The successor: its link is in the links array, a lag of one day.',
},
},
{
id: 'deps-a',
name: 'dependencies source',
start: '2026-03-16',
end: '2026-03-17',
data: {
group: 'Links',
note: 'The predecessor of a link its successor lists as a dependency.',
},
},
{
id: 'deps-b',
name: 'dependencies target',
start: '2026-03-19',
end: '2026-03-20',
// The same link as `links-a` to `links-b`, written on the successor.
dependencies: [{ id: 'deps-a', type: 'FS', lag: '1d' }],
data: {
group: 'Links',
note: 'The successor: it lists the link as a dependency, a lag of one day.',
},
},
];
export const links: Link[] = [{ source: 'links-a', target: 'links-b', type: 'FS', lag: '1d' }];

Files

  • data.ts: fourteen tasks in four groups, the links array, and Meta, the type of task.data.
  • options.ts: the granularity, dates, the size of the rows, and a tooltip that reads the typed data.
  • helpers.ts: inputOf writes a task as source text, describe says how the chart read it from its computed record.
  • vanilla.ts and react.tsx: the chart, the checkbox and the select.

Options

Option Value What it does
scheduling.granularity '1h' The step of the data: every date, duration and lag is a multiple of it, so timed tasks fit
dates.endInclusive true Whether a date-only end is the last day (the default) or the day after it; the checkbox
viewMode 'day' The scale; the select adds 'half-day' and 'quarter-day' (React: state, a controlled prop)
scroll.initial 'first-task' Opens on the plan
layout.rowHeight, layout.barHeight 28, 18 Smaller rows, so that all fourteen fit
display.barLabel 'outside' The name goes beside the bar, whatever its length
tooltip.render a function Shows how the chart read the task, and the note of its typed data
onTaskClick a function Writes the task’s input and its record in the line under the chart

How each shape is read

You write The chart reads
start and end as a Date, '2026-03-16' or a number, at local midnight A date-only value: precision 'day'. The task covers whole days, and an end is the last day (dates.endInclusive: true)
'2026-03-23T09:00', or a Date or a number with a time of day A time: precision 'time'. The task starts and ends at exact moments, and an end is the moment it is over
duration: 3 A number of days, counted from start. It is used when there is no end: end wins if both are given
duration: '3d', '1w', '6h' A length with a unit. A week is seven calendar days. Hours make a timed task, whatever start looks like
precision: 'time' on date-only values The same strings read as exact moments: '2026-03-25' is the midnight that starts that day, so the task ends there
precision: 'day' Whole days. It is what date-only values are anyway; a time of day under it would be misaligned
dates.endInclusive: false A date-only end is the day after the task, which ends when the day before it is over. Timed tasks and durations do not change
an entry of links A link: source, target, a type ('FS' by default) and a lag (a number of days, or a duration such as '1d')
dependencies on the successor The same link, written on the task that follows: ids, or { id, type, lag }. It is merged into the links
data Yours. The chart never reads it; Task<Meta> types it everywhere it comes back

What to try

  • Compare the first three bars. Date, an ISO date and an epoch number are the same five days (16 to 20 March). Click each to see the source text next to what the chart read.
  • Flip dates.endInclusive. Every bar with a date-only end (the first three, precision ‘day’ and the four link rows) loses its last day: with false the end is where the task stops, not its last day. The timed tasks, the durations and precision ‘time’ do not move.
  • Look at the times. ISO date-time and duration ‘6h’ are timed tasks: they begin and end inside a day. Choose quarter-day in the select to read them.
  • Compare the two precisions. Both tasks are written '2026-03-23' to '2026-03-25'. One is read as three whole days, the other as two days that end at midnight.
  • Compare the two links. The arrows between the links[] rows and between the dependencies rows are the same link, a finish-to-start with a lag of a day. Click the targets to see where each was written.

Notes

  • The dates are floating wall-clock time. '2026-03-23T09:00' is 09:00 for every viewer, in every time zone, and strings with a Z or a UTC offset are rejected. A Date or a number is read through the local clock of the machine, so new Date(2026, 2, 16) (months count from 0) is 16 March everywhere. Avoid new Date('2026-03-16'): JavaScript reads that string as UTC, which is the evening of the 15th west of Greenwich. The epoch numbers of this example are made from a Date for that reason.
  • The granularity decides what a time of day may be. With the default '1d', a timed task is reported as misaligned and widened to whole days. '1h' accepts the hours of this plan; a finer value accepts finer times. Tasks given as whole days stay on whole days (scheduling.keepDayPrecision, on by default).
  • Precision is derived from the input, and precision overrides it. A date-only value is a day; a time of day makes the task timed, and so does a duration in hours.
  • record has the computed side. start, end and endExclusive are Dates, endExclusive being the end of the half-open interval whichever way end was written, and duration counts days and working days. record.task is your task, untouched, with its typed data.
  • links is the canonical form, dependencies a shorthand. Use whichever fits your data: the chart merges them, the links array first, then the dependencies in task order. If you keep the links in your own state (see Controlled data), prefer the array: a dependency is not in the list the chart hands back, and deleting one is only reported to onLinkDelete.
  • Task<Meta> is the type of your payload. It flows through createGantt<Meta> or the tasks of <Gantt> into the records, the tooltip’s context and every handler, here record.task.data?.note.
  • React: the two options the controls change are state. viewMode is a controlled prop in React (the chart shows the one your state holds), and dates comes from state too. The component passes both after the spread options, so they win. The vanilla chart changes them with setViewMode and setOptions.