TimelineModel<TResource, TActivity> class Pro

The timeline's data model: resources, activities, and the ASSIGNMENT between them — the shape every established product uses (DevExpress mappings, Bryntum's resource/event/assignment stores, Kendo's four data sources, dhtmlx's flat stores).

Separate lists plus an explicit assignment rather than "each resource carries its activities": an activity can then move between resources without rebuilding either list, which is exactly what a P5 drag does, and one activity can be assigned to several resources (a machine AND its operator) without being duplicated. The assignment is a KEY, not a reference, because the row identity a grid works with is the item — and an item must stay findable after a reload replaced the objects.

The model answers the two questions the view asks per frame: how TALL is a resource row (how many lanes does it need) and WHICH activities fall into the visible time window. Both are precomputed per resource, because a frame must not sort.

public sealed class TimelineModel<TResource, TActivity>
Namespace
ArionUI.Timeline
Package
ArionUI.Timeline · dotnet add package ArionUI.Timeline --prerelease

Constructors

TimelineModel

public TimelineModel(IReadOnlyList<TResource> resources, IReadOnlyList<TActivity> activities, Func<TResource, object> resourceKey, Func<TActivity, object> assignedTo, Func<TActivity, TimeInterval> interval);

Properties

Resources

public IReadOnlyList<TResource> Resources { get; }

Unassigned

public int Unassigned { get; }

How many activities named a resource that does not exist. Not an error the model throws on — data arrives incomplete — but a number the application can show.

Methods

ActivitiesIn

public void ActivitiesIn(int resourceIndex, DateTime from, DateTime to, List<PlacedActivity<TActivity>> result);

The same query into a caller-owned buffer, for the frame path. The method clears and fills the buffer and keeps no reference.

ActivitiesIn

public IReadOnlyList<PlacedActivity<TActivity>> ActivitiesIn(int resourceIndex, DateTime from, DateTime to);

The activities of a resource that reach into the window [from, to) — the per-frame question.

Binary search for the last activity that STARTS before the window, then walk backwards while any earlier activity can still reach in (see Row.MaxEndUpTo). A long activity spanning the whole window is found that way without scanning the row; the walk stops as soon as it cannot pay off.

ActivitiesOf

public IReadOnlyList<PlacedActivity<TActivity>> ActivitiesOf(int resourceIndex);

Every activity of a resource, in start order, with its lane.

IndexOfResource

public int IndexOfResource(object? resource);

A resource's row by identity. The template asks this per row per frame and per pointer move, so the index is built once and lives as long as the model.

LaneCountOf

public int LaneCountOf(int resourceIndex);

Lanes needed by a resource (at least 1, so an empty row still has height).

Reassign

public bool Reassign(TActivity activity, int fromIndex, int toIndex);

Move one activity to another resource row (a vertical drag) and rebuild both rows; same row = a refresh. Identity is the activity OBJECT — the drag holds the very instance the model handed out via PlacedActivity. False when the activity is not in the source row (a stale drag mutates nothing).

RefreshResource

public void RefreshResource(int resourceIndex);

Rebuild one resource row from its OWN activities, re-reading every interval — the after-a-mutation step (P5): a move/resize changed spans, and sort order, lanes and the early-out maxima all derive from them. O(row), not O(model).

TotalLanes

public int TotalLanes();

Total lanes over all resources — the row-height sum a scroll extent needs.