BlazorResourceTimeline 0.4.0
See the version list below for details.
dotnet add package BlazorResourceTimeline --version 0.4.0
NuGet\Install-Package BlazorResourceTimeline -Version 0.4.0
<PackageReference Include="BlazorResourceTimeline" Version="0.4.0" />
<PackageVersion Include="BlazorResourceTimeline" Version="0.4.0" />
<PackageReference Include="BlazorResourceTimeline" />
paket add BlazorResourceTimeline --version 0.4.0
#r "nuget: BlazorResourceTimeline, 0.4.0"
#:package BlazorResourceTimeline@0.4.0
#addin nuget:?package=BlazorResourceTimeline&version=0.4.0
#tool nuget:?package=BlazorResourceTimeline&version=0.4.0
BlazorResourceTimeline
A high-performance resource-timeline / planner component for ASP.NET Core Blazor, with pluggable canvas, SVG and HTML renderers (canvas by default).
It renders a wide time window along the horizontal axis and a list of resources along the vertical axis, drawing allocation bars in the grid between them. It is built for dense, glanceable planning boards - flight/gate planning, train scheduling, fleet and crew rostering, and similar transport use-cases - where a lot of data must stay readable and interactive.

<sub>The demo in its dark theme: 68 resources and ~3,300 allocations on the canvas renderer.</sub>
Features
- Pluggable renderers: one engine (data, layout, interaction) driving your
choice of renderer via
Options.Renderer- Canvas (default; HiDPI/Retina crispness viaResizeObserver+device-pixel-content-box, fastest for dense data), SVG (resolution-independent, inspectable, print friendly) or HTML (each bar is a real, CSS-styleable element). Per-frame culling keeps all three bounded by what is visible, and the renderer can be switched at runtime. - Zoom from a multi-day overview down to hour-level detail
(
Ctrl/Cmd+ mouse wheel, trackpad pinch, or the programmatic API), with adaptive tick/label density.ZoomToDaysAsync(days)fits a given number of days into the current viewport. - Viewport control: open with the current time centered
(
Options.AutoScrollToNow), keep the view where it was across a data reload (Options.PreserveScrollOnReload), and set how often the "now" line catches up with the wall clock (Options.NowLineRefreshMs). - Day/week navigation: step the view a day at a time - or a week - with
PanByDaysAsync(). Hosts can put the same steps on their own keyboard shortcuts. - Selection: click,
Ctrl/Cmd-click to toggle, and click-and-drag marquee selection. - Editing (opt-in): drag a bar to move it in time (or onto another resource), or grab an edge to resize it, with configurable snapping and a change callback back to .NET.
- Context menu (right-click): the native browser menu is suppressed and a callback reports what was hit - the bar, the resource row, the time under the pointer and the click's viewport coordinates - so you can render your own menu.
- Overlap stacking: allocations that overlap in time on the same row are
automatically stacked into lanes instead of drawn on top of each other, and
the row grows to keep the whole stack inside it.
Options.ResourceHeightis the minimum row height;Options.BarMarginsets the gap between lanes. - Hover tooltips: per-bar tooltips (custom text or an auto-generated default), on by default and configurable.
- Resource hierarchy: nest resources into multi-level, collapsible groups
via
ParentId; click a group row (or use it from data) to expand/collapse. - On-demand (windowed) loading: for effectively unbounded datasets, serve only the visible time window (plus a buffer) via a callback; the renderer refetches as the user scrolls/zooms.
- Resource-column template: replace the renderer-drawn resource labels with a rich, interactive HTML template per row (badges, links, avatars, …).
- Keyboard & screen-reader accessible: focusable region with
role/aria-label, arrow-key bar navigation, keyboard selection, and live-region announcements. - Time-zone-aware axes (IANA ids via
Intl), correct across DST, with an optionalLocalefor day labels, tooltips and announcements, and an optional second hour row in UTC (Options.ShowUtcTime) above the local one. - Touch & pen support via Pointer Events.
- Streaming data load for very large datasets (batched interop instead of one giant payload).
- Theming: every painted color is overridable through
Options.Colors, so a light, dark or brand palette is just anotherOptionsinstance - switchable at runtime (the demo ships a dark theme). - Rich bars: per-bar colors and heights, labels (above/below/start/end), image/SVG icons anchored to any side, and start/end "edge" (delay) bars.
- Dimensions & fonts: axis sizes, row height, bar sizing and every label font are configurable.
Installation
dotnet add package BlazorResourceTimeline
Targets net8.0, net9.0, and net10.0, for both Blazor WebAssembly and
Blazor Server / interactive-render-mode apps.
No script or stylesheet registration is needed: the JavaScript engine is imported
on demand from _content/BlazorResourceTimeline/, and the component's scoped CSS
arrives through the framework's own bundle. That bundle is linked by every Blazor
template already - just make sure your host page keeps its
<link href="YourApp.styles.css" rel="stylesheet" />
line (with YourApp being your app's assembly name).
Trimming
The assembly is marked trim-compatible, so trimmed apps (Blazor WebAssembly trims on publish) shrink it rather than skipping it. The types crossing the JavaScript interop boundary are rooted in an embedded trimming descriptor, so every option and allocation property keeps working after trimming - nothing needs configuring on your side.
Ahead-of-time (AOT) compilation is not declared: interop marshalling relies on
reflection-based System.Text.Json, so the component is not AOT-safe.
Quick start
Add the component and give it a Config:
@using BlazorResourceTimeline
<div style="height: 600px;">
<BlazorResourceTimeline Config="_config"
OnSelectionChanged="OnSelectionChanged" />
</div>
@code {
private BlazorResourceTimelineConfig? _config;
protected override void OnInitialized()
{
_config = new BlazorResourceTimelineConfig
{
StartDate = DateTimeOffset.UtcNow.Date,
EndDate = DateTimeOffset.UtcNow.Date.AddDays(1),
Resources =
[
new() { Id = "gate-a1", Name = "Gate A1" },
new() { Id = "gate-a2", Name = "Gate A2" },
],
Allocations =
[
new()
{
Id = "f-100",
ResourceId = "gate-a1",
StartTime = DateTimeOffset.UtcNow.Date.AddHours(8),
EndTime = DateTimeOffset.UtcNow.Date.AddHours(10),
TextAbove = "LH441",
},
],
};
}
private void OnSelectionChanged(BlazorResourceTimelineAllocation[] selected)
{
// selected are the same instances you supplied in Config.Allocations.
}
}
The component sizes itself to its container, so give the wrapping element a height.
Note:
Configis compared by reference. Assign a newBlazorResourceTimelineConfiginstance to trigger a re-render, or callReloadAsync()after mutating the existing one in place.
Renderers
One engine owns the data, layout, hit-testing and interaction; only the painting
step differs. Pick it with Options.Renderer, and switch at runtime by assigning
a new Options instance - data, events and keyboard behavior are identical
across all three.
Renderer |
Output | Best for |
|---|---|---|
Canvas (default) |
Immediate-mode 2D drawing, HiDPI/Retina-crisp via ResizeObserver + device-pixel-content-box |
Dense boards; the fastest option |
Svg |
Resolution-independent vector nodes | Inspecting, copying or printing the scene |
Html |
One real DOM element per visible bar, carrying data-bar-id |
Styling bars with your own CSS |
All three cull to the visible viewport every frame, so cost tracks what is on screen rather than the size of the dataset.
<BlazorResourceTimeline Config="_config" Options="_options" />
@code {
private BlazorResourceTimelineOptions _options = new()
{
Renderer = BlazorResourceTimelineRendererType.Svg,
};
}
Configuration (Options)
Pass a BlazorResourceTimelineOptions to customize appearance and behavior
(dimensions, fonts, colors, time zone, zoom scale). Assign a new Options
instance to re-apply at runtime (e.g. to switch themes or time zone):
<BlazorResourceTimeline Config="_config" Options="_options" />
@code {
private BlazorResourceTimelineOptions _options = new()
{
TimeZone = "UTC",
ResourceHeight = 44, // minimum row height; rows grow to fit stacked bars
BarHeight = 8,
BarMargin = 2, // gap between bars that overlap in time on the same row
Colors = new() { Bar = "#74c0fc", Now = "#e03131" },
};
}
Every property is nullable, so a partial instance overrides only what it sets:
values left null keep the renderer's defaults. Note that re-applying Options
merges - a null property keeps whatever was applied before, rather than
resetting it to the default.
Where the view starts, and what a reload does to it
Three independent options decide where the viewport sits and how lively the "now" line is. None of them affects the others:
@code {
private BlazorResourceTimelineOptions _options = new()
{
AutoScrollToNow = true, // open with "now" centered
PreserveScrollOnReload = true, // a reload keeps the current view
NowLineRefreshMs = 1000, // advance the "now" line every second
};
}
AutoScrollToNowcenters the current time in the content area as soon as the first data load is laid out - whereGoToTodayAsync()would leave it, minus the scroll animation - so a timeline meant to open "at now" needs no call after rendering. Only the first load is affected; from then on the viewport belongs to the user. Nothing happens if "now" is outside the loaded range.PreserveScrollOnReloadkeeps a reload showing what it was showing: the time at the left edge of the content area, and the row at the top. Both are restored by time and resource id rather than by pixel offset, so the view holds even when the reload shifts the overall range, changes the scale or re-orders the rows. If the anchored row is gone entirely, the vertical position is left as it was.NowLineRefreshMsis how often the "now" line is repainted so it keeps up with the wall clock on a timeline nobody is touching (default60000- once a minute). Repaints are skipped while the tab is hidden and whenever the line would land on the same pixel, so a one-second interval costs nothing on a zoomed-out view.0stops the ticking.
Stepping through the timeline a day at a time
PanByDaysAsync(days) moves the view forward or back by whole days without
touching the zoom, keeping the same time of day at the leading edge - so a
planner can be walked day by day (or week by week, with 7) from your own
toolbar:
<button title="Back one day (←)" @onclick="() => _timeline.PanByDaysAsync(-1)">‹ Day</button>
<button title="Forward one day (→)" @onclick="() => _timeline.PanByDaysAsync(1)">Day ›</button>
<button title="Forward one week (Ctrl+→)" @onclick="() => _timeline.PanByDaysAsync(7)">Week »</button>
<BlazorResourceTimeline @ref="_timeline" Config="_config" Options="_options" />
The component does not bind these steps to the arrow keys itself: ←/→
always move the roving bar focus. Wire the same PanByDaysAsync calls from
your own page-level key handler if you want ←/→ to step a day and
Ctrl/Cmd+←/→ a week. A pan is clamped to the timeline's range, so a
press at either end does nothing, and the new leading time is announced
through the live region.
Fitting a number of days into the viewport
ZoomToDaysAsync(days) sets the horizontal scale so exactly that many days
fill the content area (the viewport minus the resource axis), keeping the
time under the center where it is. Useful when a planner should open - or
switch - to a one-day, three-day or week view without the host computing
pixels-per-hour from the layout:
<button @onclick="() => _timeline.ZoomToDaysAsync(1)">1 day</button>
<button @onclick="() => _timeline.ZoomToDaysAsync(3)">3 days</button>
<button @onclick="() => _timeline.ZoomToDaysAsync(7)">7 days</button>
<BlazorResourceTimeline @ref="_timeline" Config="_config" Options="_options" />
The resulting scale is clamped to Options.MinPixelsPerHour /
Options.MaxPixelsPerHour. A non-positive or non-finite value is ignored
and the current scale is returned.
Dual time rows (UTC)
Options.ShowUtcTime = true adds a second row of hour labels to the time axis,
in UTC, between the day labels and the row that follows TimeZone - useful for
aviation, operations and any other schedule read in Zulu time alongside a local
clock:
@code {
private BlazorResourceTimelineOptions _options = new()
{
TimeZone = "Europe/Berlin",
ShowUtcTime = true,
TimeAxisHeight = 76, // the two hour rows split the band below the day row
};
}
The UTC row is rendered exactly like the zone row - same ticks, same density,
whole-hour labels - but on UTC's own hour boundaries. Where the axis zone's
offset carries minutes, as Asia/Kolkata (+05:30) does, that puts its numbers
horizontally between the zone row's rather than beneath them:
Mon, Jun 15 <- day row, in the axis zone
00 02 04 06 08 <- UTC row
06 08 10 12 14 <- Asia/Kolkata row, shifted by :30
Under a whole-hour offset the two rows line up and differ only in their numbers,
and with TimeZone = "UTC" they read the same. The band below the day row is
split evenly between them, so raise TimeAxisHeight (default 60) to give them
more room.
The rows are not captioned - use TopStartContent to
label them in the otherwise blank top-start corner, as the demo does.
Theming and dark mode
Options.Colors (BlazorResourceTimelineColors) covers everything the renderer
paints - backgrounds, axis borders, ticks and labels, grid lines, bar fill,
selected fill/outline, bar labels, the "now" line, the marquee rectangle, the
focus ring and the tooltip. Assigning a new Options instance repaints
immediately, which is all a theme toggle needs:
<BlazorResourceTimeline Config="_config" Options="_options" />
@code {
private bool _dark;
private BlazorResourceTimelineOptions _options = new() { Colors = Light };
private void ToggleTheme()
{
_dark = !_dark;
// A new instance: Options is compared by reference.
_options = new BlazorResourceTimelineOptions { Colors = _dark ? Dark : Light };
}
private static readonly BlazorResourceTimelineColors Light = new()
{
ContentBg = "#ffffff", AxisBg = "#f8f9fa", AxisBorder = "#dee2e6",
Tick = "#adb5bd", Label = "#495057", DateLabel = "#212529", Grid = "#e9ecef",
Bar = "#74c0fc", BarSelected = "#4dabf7", BarSelectedBorder = "#1971c2",
BarLabel = "#495057", Now = "#e03131",
SelectionFill = "rgba(77, 171, 247, 0.18)", SelectionBorder = "#4dabf7",
Focus = "#1971c2", TooltipBg = "#212529", TooltipText = "#ffffff",
};
private static readonly BlazorResourceTimelineColors Dark = new()
{
ContentBg = "#1a1d21", AxisBg = "#141618", AxisBorder = "#2c3035",
Tick = "#6c757d", Label = "#adb5bd", DateLabel = "#e9ecef", Grid = "#2c3035",
Bar = "#4dabf7", BarSelected = "#74c0fc", BarSelectedBorder = "#a5d8ff",
BarLabel = "#ced4da", Now = "#ff6b6b",
SelectionFill = "rgba(77, 171, 247, 0.22)", SelectionBorder = "#74c0fc",
Focus = "#74c0fc", TooltipBg = "#f8f9fa", TooltipText = "#212529",
};
}
Note: colors are merged, not replaced - a
nullentry keeps whatever was applied before, not the built-in default. That is convenient for tweaking one color, but when swapping between themes send a complete palette, otherwise stray colors from the previous theme stick around.
Bar appearance
Beyond its time span, each BlazorResourceTimelineAllocation can carry its own
presentation:
| Property | Effect |
|---|---|
Color |
CSS color for the bar fill (falls back to Colors.Bar). |
Height |
Per-bar height in pixels (falls back to Options.BarHeight). Edge bars share it. |
TextAbove / TextBelow |
Labels centered above / below the bar. |
TextStart / TextEnd |
Labels just outside the start / end edge. |
Icons |
Images or data-URI SVGs anchored Start, End, Above, Below or Center. Several at one position lay out side by side, growing away from the bar - or as one centered group for Center. Inside = true moves an icon within the bar, against the edge its position names (Center is always inside). |
StartBar / EndBar |
Decorative "edge" bars extending before the start / after the end, each with its own Duration and Color - typically delays. |
Tooltip |
Hover text (see Tooltips). |
new BlazorResourceTimelineAllocation
{
Id = "f-100",
ResourceId = "gate-a1",
StartTime = start,
EndTime = start.AddHours(2),
Color = "#74c0fc",
Height = 12,
TextAbove = "LH441",
TextEnd = "FRA",
// 25 minutes late off-blocks, drawn in red after the planned end.
EndBar = new() { Duration = TimeSpan.FromMinutes(25), Color = "#e03131" },
Icons = [new() { Source = "/icons/warning.svg", Position = BlazorResourceTimelineBarIconPosition.Start }],
}
Labels and icons are skipped on bars narrower than Options.MinBarWidthForLabels,
so zoomed-out boards stay readable. Edge bars and icons are decoration only -
they are not selectable and never become hit targets.
Editing
Set Options.Editable = true to let users move and resize allocations directly
on the timeline (mouse/pen):
- Move – drag a bar's body to shift it in time. Unless
AllowResourceChangeisfalse, dragging vertically also reassigns it to the resource row under the pointer. - Resize – drag within
EditResizeHandlePxof a bar's start or end edge. - Snapping – moves and resizes snap to
EditSnapMinutes(default 15; set0for continuous). A resize never shrinks a bar belowEditMinDurationMinutes.
<BlazorResourceTimeline Config="_config"
Options="_options"
OnAllocationChanged="OnAllocationChanged" />
@code {
private BlazorResourceTimelineOptions _options = new()
{
Editable = true,
EditSnapMinutes = 15,
// AllowResourceChange = false, // to lock rows and only edit in time
};
private void OnAllocationChanged(BlazorResourceTimelineAllocation edited)
{
// `edited` is the same instance from Config.Allocations, already updated
// in place with its new StartTime/EndTime/ResourceId. Persist it here.
}
}
Editing is also keyboard accessible: focus a bar and use Alt+arrows to move it,
Alt+Shift+←/→ to resize the end edge, Alt+Shift+↑/↓ to resize the
start edge, and Alt+↑/↓ to change resource.
The renderer applies edits optimistically (the bar updates immediately). To
reject an edit, revert the instance in your handler and call ReloadAsync().
Context menu (right-click)
Right-clicking the timeline suppresses the browser's own menu and raises
OnContextMenu with everything needed to show your own. The args identify what
was under the pointer and where the click happened on screen:
| Property | Description |
|---|---|
Allocation |
The bar under the pointer, or null when the click missed every bar. Your own instance from Config. |
Resource |
The row under the pointer; null below the last row. |
Time |
The time at the pointer's horizontal position; null on the resource axis (which has no time coordinate). |
ClientX / ClientY |
Viewport coordinates of the click, suited to a position: fixed menu. |
It fires for bars, for empty space in the content area, and for resource-axis rows - but not for the time axis. Right-clicking never changes the selection, so an existing multi-selection survives opening a menu.
<BlazorResourceTimeline Config="_config" OnContextMenu="ShowMenu" />
@if (_menu is { } menu)
{
<div class="my-menu" style="position:fixed;left:@((int)menu.ClientX)px;top:@((int)menu.ClientY)px">
@if (menu.Allocation is { } bar)
{
<button @onclick="() => Delete(bar)">Delete @bar.Id</button>
}
else if (menu.Time is { } time)
{
<button @onclick="() => Create(menu.Resource, time)">New allocation here…</button>
}
</div>
}
@code {
private BlazorResourceTimelineContextMenuArgs? _menu;
private void ShowMenu(BlazorResourceTimelineContextMenuArgs args) => _menu = args;
}
Remember to close the menu yourself (for example from a backdrop click or
Escape) - the component only reports the event.
Overlapping allocations
Allocations that overlap in time on the same resource row are laid out in vertical lanes rather than drawn on top of each other. Overlapping bars are grouped into clusters, each bar takes the first lane free at its start time, and the cluster is centered on the row's center line. A bar that overlaps nothing keeps sitting exactly on that line, so simple rows look unchanged.
Options.BarMargin (default 2) sets the vertical gap between stacked bars;
0 stacks them touching. Lane positions account for the actual height of the
bars in each lane - Options.BarHeight or an allocation's own Height - so a
cluster mixing bar heights still lays out without overlap. Bars that merely touch
(one ends the instant the next starts) are not treated as overlapping.
<BlazorResourceTimeline Config="_config" Options="_options" />
@code {
private BlazorResourceTimelineOptions _options = new()
{
BarHeight = 10,
BarMargin = 4, // 4 px between bars that overlap in time
};
}
Rows grow to fit their stack, so a dense cluster never spills into the
neighbouring row. Options.ResourceHeight is the minimum row height: a row
whose tallest cluster needs sum(lane heights) + BarMargin × (lanes − 1) pixels
grows to that height plus the same top/bottom padding a single default-height bar
has in a minimum-height row. A row whose bars never overlap stays exactly at
ResourceHeight, so simple boards look unchanged, and the resource column,
hit-testing, keyboard navigation and the ResourceTemplate overlay all follow
the per-row heights.
Resource-column template
By default the sticky resource column is drawn by the renderer (fast, plain text).
For richer, interactive content set ResourceTemplate: the renderer then stops
drawing the labels and an HTML overlay renders your template once per visible row
(row counts are bounded, so this stays cheap). Group rows automatically get an
expand/collapse chevron before your content, and the overlay follows vertical
scroll. The context exposes the resource, its Depth, and its group state.
<BlazorResourceTimeline Config="_config">
<ResourceTemplate Context="row">
<span class="res-cell">
<strong>@row.Resource.Name</strong>
@if (row.HasChildren) { <span class="badge">group</span> }
</span>
</ResourceTemplate>
</BlazorResourceTimeline>
On-demand (windowed) loading
For datasets too large to send up front, set LoadAllocationsAsync. The timeline
then requests only the allocations overlapping the currently needed time window
(the visible range widened by a buffer) and calls the delegate again as the user
scrolls or zooms toward the edge of the loaded window. Config still supplies the
resources and the overall StartDate/EndDate; its Allocations are ignored.
<BlazorResourceTimeline Config="_config" LoadAllocationsAsync="LoadWindowAsync" />
@code {
// Return every allocation whose span overlaps the requested window, across
// all resources. Query your database/service here.
private async Task<IReadOnlyList<BlazorResourceTimelineAllocation>> LoadWindowAsync(
BlazorResourceTimelineWindow window)
{
return await _repository.GetAllocationsAsync(window.Start, window.End);
}
}
Requests are debounced, coalesced, and tagged so a slow fetch superseded by newer
scrolling is discarded rather than overwriting the current window. Tune the buffer
and refetch sensitivity with Options.WindowBufferFactor,
Options.WindowRefetchThreshold and Options.WindowDebounceMs.
Resource hierarchy
Resources form a flat list by default. Set a resource's ParentId to the Id of
another resource to nest it, building a multi-level tree. A resource that has
children renders as a collapsible group header (indented by its depth, with a
chevron); clicking the header row toggles it, and Collapsed = true starts a
group collapsed. Groups can still own their own allocations. Sibling and root
order follows the order of the resources list.
var resources = new List<BlazorResourceTimelineResource>
{
new() { Id = "dc", Name = "Data Center" },
new() { Id = "srv", Name = "Servers", ParentId = "dc" },
new() { Id = "s1", Name = "Server-01", ParentId = "srv" },
new() { Id = "s2", Name = "Server-02", ParentId = "srv" },
new() { Id = "db", Name = "Databases", ParentId = "dc", Collapsed = true },
new() { Id = "d1", Name = "Database-01", ParentId = "db" },
};
Tooltips
Hovering a bar (mouse/pen) shows a tooltip after Options.TooltipDelayMs
(default 300 ms). Set an allocation's Tooltip to control the text, or leave it
null for a default built from the bar's labels, resource name and time range.
Disable tooltips entirely with Options.ShowTooltips = false, and theme them via
Colors.TooltipBg / Colors.TooltipText.
Programmatic API
Capture the component with @ref to drive it from code:
| Method | Description |
|---|---|
ReloadAsync() |
Re-sends the current Config even if the reference is unchanged. |
ClearSelectionAsync() |
Clears the current selection. |
GetSelectedBarsAsync() |
Returns the selected allocations, in selection order. |
GoToTodayAsync() |
Centers "now" in view (if within range). Options.AutoScrollToNow does this on the first load without a call. |
ScrollToTimeAsync(unixMs) |
Centers the given time in view. |
PanByDaysAsync(days) |
Steps the view forward (or back) by whole days at the current zoom; pass ±7 for a week. false when already at that end of the range. |
ZoomInAsync() / ZoomOutAsync() |
Zoom around the viewport center. |
ZoomToDaysAsync(days) |
Zooms so exactly that many days fill the current viewport, keeping the center time fixed. |
SetPixelsPerHourAsync(value?) |
Sets an explicit scale, or null for auto. |
ResetZoomAsync() |
Returns to the auto/config scale. |
GetPixelsPerHourAsync() |
Current horizontal scale. |
Keyboard shortcuts
| Key | Action |
|---|---|
← / → |
Move between allocations in the focused row |
↑ / ↓ |
Move to the nearest allocation in the adjacent row |
Home / End |
First / last allocation in the row |
Enter |
Select the focused bar (Ctrl/Cmd+Enter toggles) |
Space |
Toggle the focused bar in a multi-selection |
Escape |
Clear the selection |
PageUp / PageDown |
Pan the time axis by a viewport |
Ctrl/Cmd + + / - / 0 |
Zoom in / out / reset |
Alt + ← / → |
Move the focused bar earlier / later (editing only) |
Alt + Shift + ← / → |
Resize the focused bar's end edge (editing only) |
Alt + Shift + ↑ / ↓ |
Resize the focused bar's start edge (editing only) |
Alt + ↑ / ↓ |
Move the focused bar to the previous / next resource (editing only) |
Notable parameters
Config- resources, time window, and allocation bars.Options- visual/behavioral configuration.OnSelectionChanged- raised with the selected allocations (your own instances).OnAllocationChanged- raised after a move/resize (editing) with the updated instance.OnContextMenu- raised on right-click with the bar/resource/time under the pointer and the click's viewport coordinates.AriaLabel- accessible name (default"Resource timeline").LoadBatchSize- allocations per interop call for streaming large datasets (default10000;0sends everything at once).LoadingMinDurationMs- minimum time the loading overlay stays visible (default0).TopStartContent/LoadingContent- custom render fragments for the top-start corner and the loading overlay.
Demo
src/Demo is a Blazor WebAssembly playground for everything above: dataset size
(7 to 365 days), renderer, bar height and margin, editing, on-demand loading, the
custom resource column, time zone, zoom (including 1 / 3 / 7-day viewport
presets), and a light/dark theme toggle - plus a live view of the selection, the
last edit and the last context-menu action. The demo wires ←/→ (and
Ctrl/Cmd+arrows for a week) to PanByDaysAsync itself; those shortcuts are
not part of the component.
dotnet run --project src/Demo
Repository layout
src/BlazorResourceTimeline- the component library (C# component, models and the JavaScript engine/renderers underwwwroot).src/Demo- the Blazor WebAssembly demo.src/Tests/BlazorResourceTimeline.Tests- .NET unit tests.src/Tests/js- rendering-engine tests (node's built-in test runner).
Building
dotnet build src/BlazorResourceTimeline.slnx
dotnet test src/BlazorResourceTimeline.slnx
dotnet run --project src/Demo
The rendering engine is JavaScript and is covered by node's built-in test runner (no npm dependencies):
node --test "src/Tests/js/**/*.test.mjs"
Releasing
Packing and publishing are driven by the tag: pushing a v* tag builds, tests,
packs and pushes to NuGet, taking the package version from the tag name
(v1.2.3 produces 1.2.3).
dotnet pack src/BlazorResourceTimeline/BlazorResourceTimeline.csproj -c Release -o artifacts
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.AspNetCore.Components.Web (>= 10.0.8)
-
net8.0
- Microsoft.AspNetCore.Components.Web (>= 8.0.0)
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.