LanePacker class Pro
Assigns overlapping activities of ONE resource to LANES, so that no two activities in the same lane overlap — the row height of a resource follows from the result.
First-fit over intervals sorted by start. That is not a heuristic here: on start-sorted intervals first-fit uses exactly as many lanes as the maximum number of simultaneously running activities, which is the provable minimum (interval-graph colouring). So "how many lanes does this resource need" has one answer, not a pretty-good one.
DETERMINISTIC by construction: the sort is by (start, end, original index), so the same data always produces the same lanes — a screenshot run, a parity run between the two hosts and a re-render after scrolling must not shuffle rows. Breaking ties by the input order rather than leaving them to the sort's discretion is the whole trick.
Called PER RESOURCE, so n is one resource's activities — never the whole data set. O(n log n) for the sort; the lane search walks only the open lanes, of which there are never more than the overlap depth.
public static class LanePacker
- Namespace
- ArionUI.Timeline
- Package
- ArionUI.Timeline ·
dotnet add package ArionUI.Timeline --prerelease
Methods
MaxOverlap
public static int MaxOverlap<T>(IReadOnlyList<T> items, Func<T, TimeInterval> interval);The maximum number of activities running at the same time — the lane count without doing the assignment.
For the row HEIGHT of a resource that is off screen: the height decides the scroll extent and is needed for EVERY resource, while the lane of each activity only matters for the visible ones.
A sweep over events whose ORDER at equal timestamps carries the half-open rule: a span's end frees its lane before a start claims one, while an instant's end comes after — so [9,10) then [10,11) is depth 1, and two milestones at 10:00 are depth 2. Getting that order wrong is exactly how a timeline grows a phantom lane at every hand-over.
Pack
public static (int[], int) Pack<T>(IReadOnlyList<T> items, Func<T, TimeInterval> interval);Convenience form for callers that just want the array (a resource's cached layout). Same guarantees as LanePacker.Pack.
Pack
public static int Pack<T>(IReadOnlyList<T> items, Func<T, TimeInterval> interval, Span<int> lanes);Packs items into lanes and reports how many were needed.
lanes receives the lane index per item IN THE ORDER of items — not in sorted order, so a caller can index it with the same index it used for the item. Must be at least as long as the item list.
Returns
Number of lanes used (0 for no items). A resource's row is max(1, result) lanes tall.
