PhoenixLiveCalendar.Utils.OverlapLayout (PhoenixLiveCalendar v0.1.0)

Copy Markdown View Source

Computes side-by-side positioning for overlapping events in a time grid.

When multiple events occupy the same time range, they need to be rendered side by side rather than stacked on top of each other. This module computes the column index and total column count for each event.

Algorithm

Uses a greedy column assignment:

  1. Sort events by start time, then by duration (longest first)
  2. For each event, find the first column where it doesn't overlap with any already-placed event
  3. After all events are placed, compute the maximum column count for each group of overlapping events

Output

Returns a map of %{event_id => %{column: integer, total_columns: integer}} which the view uses to compute left and width CSS positioning.

Summary

Functions

Computes the overlap layout for a list of timed events on a single day.

Returns CSS style string for an event's horizontal position within its column.

Types

layout_info()

@type layout_info() :: %{column: non_neg_integer(), total_columns: pos_integer()}

layout_map()

@type layout_map() :: %{required(term()) => layout_info()}

Functions

compute(events)

@spec compute([PhoenixLiveCalendar.Event.t()]) :: layout_map()

Computes the overlap layout for a list of timed events on a single day.

Returns a map from event ID to %{column: N, total_columns: M}. Events that don't overlap get %{column: 0, total_columns: 1}.

Examples

events = [
  %Event{id: "a", start: ~U[2026-04-01 09:00:00Z], end: ~U[2026-04-01 10:00:00Z]},
  %Event{id: "b", start: ~U[2026-04-01 09:30:00Z], end: ~U[2026-04-01 10:30:00Z]},
  %Event{id: "c", start: ~U[2026-04-01 11:00:00Z], end: ~U[2026-04-01 12:00:00Z]}
]

layout = OverlapLayout.compute(events)
# => %{
#   "a" => %{column: 0, total_columns: 2},
#   "b" => %{column: 1, total_columns: 2},
#   "c" => %{column: 0, total_columns: 1}
# }

position_style(arg1)

@spec position_style(layout_info()) :: String.t()

Returns CSS style string for an event's horizontal position within its column.

Examples

style = OverlapLayout.position_style(%{column: 1, total_columns: 3})
# => "left: 33.33%; width: 33.33%"