Planning matrix

ProPackage ArionUI.Planning · free during the preview, also commercially

A time axis across, rows down, and one or more values per row and time slot: demand per article per day, hours per person per week. The builder produces ordinary grid columns, so sorting, filtering, editing, copy and export work on a plan as on any table.

Install

Terminal
dotnet add package ArionUI.Planning --prerelease

Building columns with PlanningBuilder<T> switches the module on. There is no planning view: a regular GridView<T> carries the columns.

Building a plan

PlanningBuilder
var plan = new Dictionary<(string Article, DateOnly Day), double>();

var axis = new TimeAxisDefinition(new DateOnly(2026, 11, 2), new DateOnly(2026, 11, 29),
                                  TimeBucket.Day);   // one column per day, banded by week

var columns = new PlanningBuilder<Article>()
    .TimeAxis(axis)
    .Descriptor("Article", a => a.Name, width: 160)
    .Measure("Quantity",
        get: (a, slot) => plan.TryGetValue((a.Name, slot.Start), out var q) ? q : null,
        set: (a, slot, v) => plan[(a.Name, slot.Start)] = v ?? 0,
        rollup: AggregateFn.Sum)
    .Calculated("Value", cell => cell.Row is { } a ? cell["Quantity"] * (double)a.Price : null)
    .Rollup()                       // a total column per band for measures with a rollup
    .Sparkline("Quantity")          // the trend over the whole axis
    .States(today: true, weekend: true)
    .Build();

grid.MainView = new GridView<Article>(columns, new ListItemsSource<Article>(articles)).AsDataGrid();
TimeAxis(definition)
The range and the bucket — Day, Week or Month — plus the first day of the week, the culture for labels and the date treated as today. Day columns are banded by week.
Descriptor(header, text)
A leading column: the article, the person. An overload takes any ready-made column — a badge, a checkbox, a composed cell.
Measure(header, get, set, format, rollup, width, warn)
One value per row and slot. The getter receives the row and the TimeSlot, so no code ever works out which day a column is about. With a setter the cells are editable through the normal edit path: undo, paste and fill-down apply. warn marks cells that need attention.
Calculated(header, formula)
A read-only value computed from the other measures of the same cell, by name: cell["Quantity"]. An unknown name throws instead of reading as zero.
Rollup()
One total column per band — per week for day columns — for every measure that declared a rollup function.
Sparkline(measure)
A trend column: the measure over the whole axis, drawn in the cell.
States(today, weekend, dimReadOnly)
Washes for today and weekends, dimmed read-only measures. All on by default.

Exceptions and visible measures

BuildExceptionFilter() returns a predicate that keeps the rows in which any warn condition holds; pass it to view.SetQuickFilter for an "exceptions only" switch. VisibleMeasures(headers) builds columns for some measures only, while formulas still see all of them.

PlanningLayout captures the axis and the visible measures — PlanningLayout.Capture(axis, visibleMeasures) — and turns them back into an axis with ToAxis(culture, today). An ArionLayout from layout persistence has a slot for it.

Freeze the descriptor columns so they stay in view while the time axis scrolls: grid.Api.Layout.FrozenColumns = 1.