# `GanttEx.Timeline`
[🔗](https://github.com/rocket4ce/gantt_ex/blob/v0.1.0/lib/gantt_ex/timeline.ex#L1)

Maps time to pixels.

The timeline is divided into columns of the *last* (most detailed) scale.
Every column has the same width (`:column_width`), even when the underlying
periods do not (months have 28 to 31 days): positions are interpolated
inside each column, exactly like DHTMLX Gantt does.

    iex> timeline = GanttEx.Timeline.new(
    ...>   scales: [[unit: :month], [unit: :day]],
    ...>   start_date: ~N[2025-01-01 00:00:00],
    ...>   end_date: ~N[2025-02-01 00:00:00],
    ...>   column_width: 40
    ...> )
    iex> GanttEx.Timeline.column_count(timeline)
    31
    iex> GanttEx.Timeline.x(timeline, ~N[2025-01-03 12:00:00])
    100.0
    iex> GanttEx.Timeline.date_at(timeline, 100)
    ~N[2025-01-03 12:00:00]
    iex> GanttEx.Timeline.width(timeline)
    1240

# `cell`

```elixir
@type cell() :: %{
  start: NaiveDateTime.t(),
  end: NaiveDateTime.t(),
  x: float(),
  width: float(),
  label: String.t() | nil
}
```

A cell of a scale row or a column of the chart.

# `t`

```elixir
@type t() :: %GanttEx.Timeline{
  boundaries: tuple(),
  column_width: pos_integer(),
  end_date: NaiveDateTime.t(),
  scales: [GanttEx.Scale.t(), ...],
  start_date: NaiveDateTime.t(),
  week_start: 1..7
}
```

# `cells`

```elixir
@spec cells(t(), GanttEx.Scale.t(), (NaiveDateTime.t(), term() -&gt; String.t())) :: [
  cell()
]
```

Returns the cells of a scale row, clipped to the timeline range and
labelled with `formatter` (see `GanttEx.I18n.formatter/1`).

    iex> timeline = GanttEx.Timeline.new(scales: [[unit: :month], [unit: :day]], start_date: ~N[2025-01-30 00:00:00], end_date: ~N[2025-02-02 00:00:00], column_width: 10)
    iex> [month, _day] = timeline.scales
    iex> timeline |> GanttEx.Timeline.cells(month, GanttEx.I18n.formatter("en")) |> Enum.map(&{&1.label, &1.x, &1.width})
    [{"Jan", 0.0, 20.0}, {"Feb", 20.0, 10.0}]

# `column_count`

```elixir
@spec column_count(t()) :: non_neg_integer()
```

Returns the number of columns.

# `columns`

```elixir
@spec columns(t()) :: [cell()]
```

Returns the columns of the most detailed scale.

    iex> timeline = GanttEx.Timeline.new(scales: [[unit: :day]], start_date: ~N[2025-01-01 00:00:00], end_date: ~N[2025-01-03 00:00:00], column_width: 50)
    iex> timeline |> GanttEx.Timeline.columns() |> Enum.map(&{&1.start.day, &1.x, &1.width})
    [{1, 0, 50}, {2, 50, 50}]

# `date_at`

```elixir
@spec date_at(t(), number()) :: NaiveDateTime.t()
```

Returns the datetime at a horizontal position (the inverse of `x/2`).

# `fit`

```elixir
@spec fit({NaiveDateTime.t(), NaiveDateTime.t()} | nil, [term()], keyword()) ::
  {NaiveDateTime.t(), NaiveDateTime.t()}
```

Computes a comfortable visible range for the given date range.

The range is padded by one column on each side and aligned to the first
(least detailed) scale, so the top header always shows complete periods.
It is then widened to at least `:min_columns` columns (defaults to `12`).
When `range` is `nil` (no tasks) the range around `:today` is used.

    iex> GanttEx.Timeline.fit({~N[2025-01-10 00:00:00], ~N[2025-01-20 00:00:00]}, [[unit: :month], [unit: :day]])
    {~N[2025-01-01 00:00:00], ~N[2025-02-01 00:00:00]}

    iex> GanttEx.Timeline.fit({~N[2025-01-01 00:00:00], ~N[2025-01-31 00:00:00]}, [[unit: :month], [unit: :day]])
    {~N[2024-12-01 00:00:00], ~N[2025-02-01 00:00:00]}

# `new`

```elixir
@spec new(keyword()) :: t()
```

Builds a timeline.

## Options

  * `:scales` - list of scales (anything accepted by `GanttEx.Scale.new/1`). Required.
  * `:start_date`, `:end_date` - the visible range. Required. The start is
    aligned to the most detailed scale.
  * `:column_width` - width of a column in pixels. Defaults to `60`.
  * `:week_start` - first ISO day of the week (`1` = Monday). Defaults to `1`.

# `unit_width`

```elixir
@spec unit_width(t(), GanttEx.Dates.unit()) :: float() | nil
```

Returns how many pixels one `unit` spans, when it can be known (the unit
and the columns have a fixed length), or `nil`.

    iex> timeline = GanttEx.Timeline.new(scales: [[unit: :week]], start_date: ~N[2025-01-06 00:00:00], end_date: ~N[2025-02-03 00:00:00], column_width: 70)
    iex> GanttEx.Timeline.unit_width(timeline, :day)
    10.0

# `width`

```elixir
@spec width(t()) :: non_neg_integer()
```

Returns the total width in pixels.

# `x`

```elixir
@spec x(t(), NaiveDateTime.t()) :: float()
```

Returns the horizontal position (in pixels) of a datetime. Dates outside the
range are extrapolated.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
