Table of Contents

Dashboard tiles & timelines

Single-panel KPI tiles and horizontal state timelines for operational dashboards, fleet status views, and monitoring UIs. Both series types are chartless — they carry no axes, just a formatted value or a coloured timeline — and are designed to be composed via mosaic or subplot layouts.

Stat tile — single KPI number

A StatTileSeries renders one big formatted headline number with an optional label beneath it. The tile fills whatever plot region it occupies; use one subplot per tile.

// "12 participants" tile — plain integer, no accent color
Plt.Create()
    .StatTile(12, t => t.Label = "Participants")
    .Save("tile_participants.svg");

The Format property follows standard .NET numeric format strings (invariant culture; default "0.##" strips trailing zeros). Set AccentColor to draw the headline in a warning or brand colour:

// Alert count in red when non-zero
int alertCount = 3;
Plt.Create()
    .StatTile(alertCount, t =>
    {
        t.Label       = "Alerts";
        t.AccentColor = alertCount > 0 ? Colors.Red : null;
        t.Format      = "0";   // integer, no decimals
    })
    .Save("tile_alerts.svg");

Multi-tile mosaic dashboard

Use Plt.Mosaic(...) to compose several KPI tiles in a grid. Each letter in the pattern string becomes a panel:

Plt.Mosaic("ABC\nDEF")
    .Panel('A', ax => ax.StatTile(12,       t => { t.Label = "Participants"; t.Format = "0"; }))
    .Panel('B', ax => ax.StatTile(0,        t => { t.Label = "Alerts";       t.Format = "0"; }))
    .Panel('C', ax => ax.StatTile(99.4,     t => { t.Label = "Uptime %";     t.Format = "0.0"; }))
    .Panel('D', ax => ax.StatTile(1_042,    t => { t.Label = "Messages/s";   t.Format = "0"; }))
    .Panel('E', ax => ax.StatTile(3.7,      t => { t.Label = "Latency ms";   t.Format = "0.0"; }))
    .Panel('F', ax => ax.StatTile(2,        t =>
    {
        t.Label       = "Errors";
        t.AccentColor = Colors.Red;
        t.Format      = "0";
    }))
    .WithTitle("Fleet Overview")
    .WithTheme(Theme.Dark)
    .WithSize(900, 300)
    .Save("fleet_tiles.svg");

Combining a tile with a chart

Place a KPI tile beside a regular chart using AddSubPlot with a GridSpec:

double[] x = Enumerable.Range(0, 60).Select(i => (double)i).ToArray();
double[] y = x.Select(v => 50 + 20 * Math.Sin(v * 0.3)).ToArray();

Plt.Create()
    .WithGridSpec(1, 2, widthRatios: [3.0, 1.0])
    .AddSubPlot(GridPosition.Single(0, 0), ax => ax
        .Plot(x, y, s => s.Label = "Signal")
        .WithTitle("Live signal")
        .WithLegend())
    .AddSubPlot(GridPosition.Single(0, 1), ax => ax
        .StatTile(y[^1], t =>
        {
            t.Label  = "Last value";
            t.Format = "0.0";
        }))
    .TightLayout()
    .Save("chart_with_tile.svg");

State timeline — discrete coloured segments

A StateTimelineSeries renders a single-row horizontal bar divided into coloured rectangles, one per StateSegment. Each segment spans [Start, End] in data units and shows a centred text label. The Y range is fixed at [0, 1] so the bar fills the full plot height.

The StateSegment record struct takes four positional arguments:

// StateSegment(Start, End, Label, Color)
var segs = new[]
{
    new StateSegment(0,   30,  "Starting",  Colors.Gray),
    new StateSegment(30,  120, "Running",   Colors.Tab10Green),
    new StateSegment(120, 140, "Degraded",  Colors.Tab10Orange),
    new StateSegment(140, 180, "Running",   Colors.Tab10Green),
    new StateSegment(180, 200, "Stopped",   Colors.Red),
};

Plt.Create()
    .StateTimeline(segs, s => s.Label = "Worker A")
    .WithTitle("Worker state over time")
    .Save("state_timeline.svg");

Date X-axis timeline

Pass OA-date X values and call SetXDateAxis() for automatic date tick formatting:

DateTime t0 = new DateTime(2026, 6, 1);
var segs = new[]
{
    new StateSegment(t0.ToOADate(),                    t0.AddHours(6).ToOADate(),  "Starting", Colors.Gray),
    new StateSegment(t0.AddHours(6).ToOADate(),        t0.AddHours(20).ToOADate(), "Running",  Colors.Tab10Green),
    new StateSegment(t0.AddHours(20).ToOADate(),       t0.AddHours(22).ToOADate(), "Stopped",  Colors.Red),
    new StateSegment(t0.AddHours(22).ToOADate(),       t0.AddDays(1).ToOADate(),   "Running",  Colors.Tab10Green),
};

Plt.Create()
    .AddSubPlot(1, 1, 1, ax => ax
        .StateTimeline(segs)
        .SetXDateAxis()
        .WithTitle("Node uptime — June 1"))
    .Save("timeline_dates.svg");

Stacked multi-row timelines (mosaic)

Stack one timeline per participant by assigning each its own subplot row:

var workerA = new StateSegment[]
{
    new(0, 40,  "Running", Colors.Tab10Green),
    new(40, 55, "Stopped", Colors.Red),
    new(55, 100,"Running", Colors.Tab10Green),
};
var workerB = new StateSegment[]
{
    new(0,  20,  "Running",  Colors.Tab10Green),
    new(20, 35,  "Degraded", Colors.Tab10Orange),
    new(35, 100, "Running",  Colors.Tab10Green),
};
var workerC = new StateSegment[]
{
    new(0, 100, "Running", Colors.Tab10Green),
};

Plt.Mosaic("A\nB\nC")
    .Panel('A', ax => ax.StateTimeline(workerA, s => s.Label = "Worker A").WithTitle("Worker A"))
    .Panel('B', ax => ax.StateTimeline(workerB, s => s.Label = "Worker B").WithTitle("Worker B"))
    .Panel('C', ax => ax.StateTimeline(workerC, s => s.Label = "Worker C").WithTitle("Worker C"))
    .WithTitle("Fleet state timeline")
    .WithSize(900, 350)
    .TightLayout()
    .Save("fleet_timelines.svg");

Tiles + timelines combined

Mix stat tiles and state timelines in a single dashboard:

var alarmHistory = new StateSegment[]
{
    new(0,  10,  "OK",    Colors.Tab10Green),
    new(10, 14,  "ALARM", Colors.Red),
    new(14, 30,  "OK",    Colors.Tab10Green),
};

Plt.Mosaic("AAB\nAAC")
    .Panel('A', ax => ax
        .StateTimeline(alarmHistory)
        .WithTitle("Alarm history (minutes)"))
    .Panel('B', ax => ax.StatTile(2, t => { t.Label = "Alerts today"; t.AccentColor = Colors.Red; t.Format = "0"; }))
    .Panel('C', ax => ax.StatTile(99.3, t => { t.Label = "Uptime %"; t.Format = "0.0"; }))
    .WithTheme(Theme.Dark)
    .WithSize(900, 400)
    .TightLayout()
    .Save("ops_dashboard.svg");

Full ops dashboard — tiles + timeline + thresholded chart

Combine all four v1.12.0 dashboard conveniences in one mosaic: a KPI tile row (StatTileSeries), a StateTimelineSeries for service health, and a line chart using the Threshold(...) convenience (dashed reference line + breach shading) together with WithLegendValues() so the legend shows the live reading:

var serviceHistory = new StateSegment[]
{
    new(0,  6,  "Up",       Colors.Tab10Green),
    new(6,  9,  "Degraded", Colors.Tab10Orange),
    new(9,  14, "Up",       Colors.Tab10Green),
    new(14, 15, "Down",     Colors.Red),
    new(15, 24, "Up",       Colors.Tab10Green),
};

double[] hours = Enumerable.Range(0, 48).Select(i => i * 0.5).ToArray();
double[] load  = hours.Select(h => 55 + 15 * Math.Sin(h * 0.4) + (h > 20 ? 20 : 0)).ToArray();

Plt.Create()
    .WithTitle("Ops Dashboard")
    .WithTheme(Theme.Dark)
    .WithSize(1000, 750)
    .WithGridSpec(3, 3, heightRatios: [1.0, 1.0, 1.6])
    .AddSubPlot(GridPosition.Single(0, 0), ax => ax
        .StatTile(12, t => { t.Label = "Participants"; t.Format = "0"; }))
    .AddSubPlot(GridPosition.Single(0, 1), ax => ax
        .StatTile(1, t => { t.Label = "Alerts"; t.AccentColor = Colors.Red; t.Format = "0"; }))
    .AddSubPlot(GridPosition.Single(0, 2), ax => ax
        .StatTile(99.4, t => { t.Label = "Uptime %"; t.Format = "0.0"; }))
    .AddSubPlot(new GridPosition(1, 2, 0, 3), ax => ax
        .StateTimeline(serviceHistory, s => s.Label = "API service")
        .WithTitle("Service state — last 24h"))
    .AddSubPlot(new GridPosition(2, 3, 0, 3), ax => ax
        .Plot(hours, load, s => s.Label = "CPU load %")
        .Threshold(80.0, Orientation.Horizontal, ThresholdBreach.Above,
            color: Colors.Red, label: "Alarm")
        .SetXLabel("Hour")
        .SetYLabel("Load %")
        .WithLegend()
        .WithLegendValues())
    .TightLayout()
    .Save("ops_dashboard_full.svg");

Ops dashboard

See Threshold convenience and Legend value display for the full parameter reference of Threshold(...) and WithLegendValues().

Operations dashboard — Plt.OpsDashboard()

A control-room screen is not a chart with more charts on it. It is a screen someone must be able to sit in front of for eight hours and still notice the one thing that changes. That is a design problem with a long-settled literature behind it, and this template follows it rather than inventing its own.

Colour is reserved. At rest the screen carries none — not even green. The moment a healthy state is coloured, the abnormal one has nothing left to stand out against, and a wall of cheerful tiles is a wall nobody looks at. Colour appears only when something needs attention, and it comes out of the theme's AlarmPalette: Okabe-Ito amber for look at this and vermillion for act now, chosen because roughly eight percent of men cannot separate red from green and these two stay apart for all of them. Colour is never the sole carrier of meaning — every coloured mark says the same thing in words too.

A silent source is hatched, not coloured. On a monitored fleet, "I can no longer see you" is the most common failure and it is not the same failure as "you are broken". A dashboard that paints them the same lies exactly when it matters. StatTileSeries.Hatch and StateSegment.Hatch exist for this.

The caller owns the clock. WithWindow(end, span) takes the end instant from you. The library never reads DateTime.Now — a figure that depends on the wall clock cannot be tested, cannot be replayed, and cannot render a dashboard for any moment but this one.

using MatPlotLibNet.Models.Series;
using MatPlotLibNet.Styling;

var now = DateTime.UtcNow;                       // the caller's clock, not the library's
var theme = Theme.OpsNight;                      // or OpsPanel / OpsWarm / OpsContrast

Plt.OpsDashboard()
    .WithTitle("Synapse — federation")
    .WithWindow(now, TimeSpan.FromMinutes(5))    // ONE window, shared by every time panel
    .WithNormalBand(2200, 2700)

    // A tile carries value + target + gap + trend. A bare big number is a failed pattern: without a
    // comparative the reader cannot tell whether it is good or bad, and must supply the missing context
    // from memory — which they mostly cannot.
    .AddTile(15, t =>
    {
        t.Label = "Buses";
        t.Caption = "all 15 normal";             // no accent → the tile wears the theme's neutral shade
    })
    .AddTile(28.1, t =>
    {
        t.Label = "RFx p99";
        t.Format = "0.0' ms'";
        t.Target = 25;
        t.Caption = "target 25 ms · +3.1 over";
        t.Trend = p99History;                    // an inline Tufte sparkline: no axis, no frame, no ticks
        t.AccentColor = theme.Alarm.Warning;     // colour ONLY because it is out of band
    })
    .AddTile(0, t =>
    {
        t.Label = "Exchange";
        t.Caption = "no contact";
        t.Hatch = HatchPattern.ForwardDiagonal;  // unknown ≠ broken — a pattern, never a colour
    })

    .AddTimeline(busSegments, l => l.Label = "Service Bus")
    .AddTrend(clock, publish, s => s.Label = "publish")
    .AddTrend(clock, consume, s => s.Label = "consume")
    .ConfigureTrend(ax => ax.SetYLabel("messages / s"))
    .Build()
    .WithTheme(theme)
    .Save("control-room.svg");

The rolling axis

WithWindow pins exact bounds and lets the locator round only the ticks. That distinction is the whole difference between a chart that glides and one that lurches: an auto axis expands its bounds outward to the nearest nice number, so it stands perfectly still while the data grows into it and then jumps a whole step at once. Pinned bounds slide continuously; round ticks glide out of frame with the data they belong to.

Bullet graphs instead of dials

BulletGraphSeries is Stephen Few's designed replacement for the radial gauge, which the high-performance-HMI literature rejects: a dial spends a quarter of a panel to say what a bar says in a fifth of it, and it cannot be stacked. The bullet keeps the measure, the target and the qualitative ranges in one thin strip — and its bands are one hue at varying intensity, never red/amber/green, so they neither exclude colour-blind readers nor spend the alarm palette on a backdrop.

ax.Bullet(2412, b =>
{
    b.Target = 2500;
    b.Bands = [new(1800, band1), new(2200, band2), new(2800, band3)];
});

Live Blazor sample

Samples/MatPlotLibNet.Samples.Blazor serves /obs-dashboard: a simulated federation of 15 buses. Measurement runs at a fixed 250 ms and never waits for a render; the refresh knob throttles the charts only — the tiles never slow down, because history may lag and a warning may not. The window (1 / 5 / 15 min, 1 hour) re-buckets the same measurements: rates keep a min/max envelope so a five-second burst survives a one-minute bucket, and percentiles carry their maximum, because a p99 cannot be averaged and averaging is precisely how a dashboard hides the spike you came to look for.

The alarm conditioning — on-delay, deadband, off-delay, the worst-child roll-up and the staleness clock — lives in the sample, not in this library. A charting library that decides when something counts as broken has started holding opinions about a domain it cannot see.

StatTileSeries — parameter reference

Property Type Default Description
Value double (constructor arg) Headline number displayed in the tile.
Label string? null Subtitle text shown beneath the headline.
AccentColor Color? null Foreground colour of the headline number. null = theme cycle colour.
Format string "0.##" .NET numeric format string applied to Value (invariant culture).

StateTimelineSeries — parameter reference

Property Type Description
Segments IReadOnlyList<StateSegment> The ordered segments that make up the timeline.

StateSegment fields

Field Type Description
Start double X-axis start of the segment (data units).
End double X-axis end of the segment (data units).
Label string Centred text rendered inside the segment rectangle.
Color Color Fill colour of the segment rectangle.

See also

  • Subplots & GridSpec — mosaic and GridSpec layout, row/column ratios
  • Financial Charts — OHLC/candlestick dashboards with indicator subplots
  • Annotations — threshold reference lines and breach shading, full Threshold(...) parameter reference
  • Line Charts — legend value display (WithLegendValues()), full parameter reference