Planning matrix
ArionUI.Planning · free during the preview, also commerciallyA 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
dotnet add package ArionUI.Planning --prereleaseBuilding columns with PlanningBuilder<T> switches the module on. There is no planning view: a regular GridView<T> carries the columns.
Building a plan
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,WeekorMonth— 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.warnmarks 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.
