Skip to content

Dashboard Engine Guide

github-actions[bot] edited this page Mar 18, 2026 · 5 revisions

Dashboard Engine Guide

Build rich, interactive dashboards with mixed widget types, sensor bindings, JSON persistence, and a visual editor.


Overview

FastSense provides two dashboard systems:

Feature FastSenseGrid DashboardEngine
Grid Fixed rows x cols 24-column responsive
Tile content FastSense instances only 8 widget types (plots, gauges, numbers, tables, etc.)
Persistence None JSON save/load + .m script export
Visual editor No Yes (drag/resize, palette, properties panel)
Scrolling No Auto-scrollbar when content overflows
Global time No Dual sliders controlling all widgets
Sensor binding Via addSensor per tile Direct widget property (auto-title, auto-units)
Live mode Per-figure timer Engine-level timer refreshing all widgets

When to use FastSenseGrid: You need a simple tiled grid of FastSense time series plots with linked axes and a toolbar.

When to use DashboardEngine: You need mixed widget types (gauges, KPIs, tables, timelines), JSON persistence, or the visual editor.


Quick Start

setup;

% Create some data
x = linspace(0, 100, 10000);
y = sin(x) + 0.1 * randn(size(x));

% Build a dashboard
d = DashboardEngine('My First Dashboard');
d.Theme = 'dark';

d.addWidget('fastsense', 'Title', 'Signal', ...
    'Position', [1 1 24 6], ...
    'XData', x, 'YData', y);

d.addWidget('number', 'Title', 'Latest Value', ...
    'Position', [1 7 8 2], ...
    'StaticValue', y(end), 'Units', 'V');

d.render();

Grid System

DashboardEngine uses a 24-column grid. Widget positions are specified as:

Position = [col, row, width, height]
  • col: column (1-24), left to right
  • row: row (1+), top to bottom
  • width: number of columns to span (1-24)
  • height: number of rows to span

Examples:

[1 1 24 4]   % Full width, 4 rows tall, top of dashboard
[1 1 12 4]   % Left half
[13 1 12 4]  % Right half
[1 5 8 2]    % Left third, row 5

If a new widget overlaps an existing one, it is automatically pushed down to the next free row.


Widget Types

FastSense (time series)

% Sensor-bound (recommended)
d.addWidget('fastsense', 'Position', [1 1 12 8], 'Sensor', mySensor);

% Inline data
d.addWidget('fastsense', 'Title', 'Raw', 'Position', [13 1 12 8], ...
    'XData', x, 'YData', y);

% From MAT file
d.addWidget('fastsense', 'Title', 'File', 'Position', [1 9 24 6], ...
    'File', 'data.mat', 'XVar', 'x', 'YVar', 'y');

When bound to a Sensor, threshold rules apply automatically (resolved violations are shown). The widget title, X-axis label ('Time'), and Y-axis label (sensor Units or Name) are auto-derived.

Number (big value display)

d.addWidget('number', 'Title', 'Temperature', ...
    'Position', [1 1 6 2], ...
    'Sensor', sTemp, 'Units', 'degF', 'Format', '%.1f');

Shows a large number with a trend arrow (up/down/flat) computed from recent sensor data. Layout: [Title | Value+Trend | Units].

Status (health indicator)

d.addWidget('status', 'Title', 'Pump', ...
    'Position', [7 1 5 2], ...
    'Sensor', sTemp);

Shows a colored dot (green/amber/red) and the sensor's latest value. Status is derived automatically from threshold rules.

Gauge (arc/donut/bar/thermometer)

d.addWidget('gauge', 'Title', 'Flow Rate', ...
    'Position', [1 3 8 6], ...
    'Sensor', sFlow, 'Range', [0 160], 'Units', 'L/min', ...
    'Style', 'donut');

Styles: 'arc' (default), 'donut', 'bar', 'thermometer'.

When Sensor-bound, range and units are auto-derived from threshold rules and sensor properties.

Text (labels and headers)

d.addWidget('text', 'Title', 'Plant Overview', ...
    'Position', [1 1 6 1], ...
    'Content', 'Line 4 - Shift A', 'FontSize', 16);

Table (data display)

% Static data
d.addWidget('table', 'Title', 'Alarm Log', ...
    'Position', [13 9 12 4], ...
    'ColumnNames', {'Time', 'Tag', 'Value'}, ...
    'Data', {{'12:00', 'T-401', '85.2'; '12:05', 'P-201', '72.1'}});

% Sensor data (last N rows)
d.addWidget('table', 'Title', 'Recent Data', ...
    'Position', [1 9 12 4], ...
    'Sensor', sTemp, 'N', 15);

Raw Axes (custom plots)

d.addWidget('rawaxes', 'Title', 'Temperature Distribution', ...
    'Position', [1 5 8 4], ...
    'PlotFcn', @(ax) histogram(ax, tempData, 50, ...
        'FaceColor', [0.31 0.80 0.64], 'EdgeColor', 'none'));

The PlotFcn receives MATLAB axes as the first argument. When Sensor-bound, it also receives the Sensor object and optionally a time range.

Event Timeline

% From event structs
events = struct('startTime', {0, 3600}, 'endTime', {3600, 7200}, ...
    'label', {'Idle', 'Running'}, 'color', {[0.6 0.6 0.6], [0.2 0.7 0.3]});

d.addWidget('timeline', 'Title', 'Machine Mode', ...
    'Position', [1 13 24 3], ...
    'Events', events);

% From EventStore
d.addWidget('timeline', 'Title', 'Alarms', ...
    'Position', [1 16 24 3], ...
    'EventStoreObj', myEventStore);

Sensor Binding

The recommended way to drive dashboard widgets is through Sensor objects. Create sensors with data, state channels, and threshold rules, then bind them to widgets:

% Create and configure sensor
sTemp = Sensor('T-401', 'Name', 'Temperature');
sTemp.X = t;
sTemp.Y = temp;

sc = StateChannel('machine');
sc.X = [0 7200 43200]; sc.Y = [0 1 0];
sTemp.addStateChannel(sc);

sTemp.addThresholdRule(struct('machine', 1), 78, ...
    'Direction', 'upper', 'Label', 'Hi Warn');
sTemp.addThresholdRule(struct('machine', 1), 85, ...
    'Direction', 'upper', 'Label', 'Hi Alarm');
sTemp.resolve();

% All of these auto-derive from the Sensor:
d.addWidget('fastsense', 'Sensor', sTemp, 'Position', [1 1 12 8]);
d.addWidget('number', 'Sensor', sTemp, 'Position', [13 1 6 2], 'Units', 'degF');
d.addWidget('status', 'Sensor', sTemp, 'Position', [19 1 6 2]);
d.addWidget('gauge', 'Sensor', sTemp, 'Position', [13 3 12 6]);

Benefits of Sensor binding:

  • Title: auto-derived from Sensor.Name or Sensor.Key
  • Units: auto-derived from Sensor.Units
  • Value: uses Sensor.Y(end) for number, gauge, and status widgets
  • Thresholds: FastSenseWidget renders resolved thresholds and violations
  • Status: StatusWidget checks the latest value against all threshold rules
  • Live refresh: calling refresh() re-reads the sensor data

Saving and Loading

Save to JSON

d.save('dashboard.json');

The JSON file contains the dashboard name, theme, live interval, grid settings, and each widget's type, title, position, and data source.

Load from JSON

d2 = DashboardEngine.load('dashboard.json');
d2.render();

To re-bind Sensor objects on load, provide a resolver function:

d2 = DashboardEngine.load('dashboard.json', ...
    'SensorResolver', @(name) SensorRegistry.get(name));
d2.render();

Export as MATLAB Script

d.exportScript('rebuild_dashboard.m');

Generates a readable .m file with DashboardEngine constructor and addWidget calls that recreates the dashboard.


Theming

DashboardEngine uses DashboardTheme, which extends FastSenseTheme with dashboard-specific fields (widget backgrounds, border colors, status indicator colors, etc.).

d = DashboardEngine('My Dashboard');
d.Theme = 'dark';        % or 'light', 'industrial', 'scientific', 'ocean'
d.render();

Available presets: 'default', 'dark', 'light', 'industrial', 'scientific', 'ocean'.


Live Mode

DashboardEngine supports live data updates via a timer that periodically calls refresh() on all widgets.

d = DashboardEngine('Live Monitor');
d.Theme = 'dark';
d.LiveInterval = 2;  % refresh every 2 seconds

d.addWidget('fastsense', 'Sensor', sTemp, 'Position', [1 1 24 8]);
d.addWidget('number', 'Sensor', sTemp, 'Position', [1 9 12 2]);

d.render();
d.startLive();   % start periodic refresh
% ... later
d.stopLive();    % stop

You can also toggle live mode from the toolbar's Live button.


Global Time Controls

The time panel at the bottom of the dashboard has two sliders that control the visible time range across all widgets. Moving the sliders calls setTimeRange(tStart, tEnd) on each widget.

  • FastSenseWidget: sets xlim on the FastSense axes
  • EventTimelineWidget: sets xlim on the timeline axes
  • RawAxesWidget: passes the time range to the PlotFcn

If a user manually zooms a specific widget, that widget detaches from global time (UseGlobalTime = false). Click the Sync button in the toolbar to re-attach all widgets.


Visual Editor

Click the Edit button in the toolbar to enter edit mode:

  1. A palette sidebar appears on the left with buttons for each widget type
  2. A properties panel appears on the right showing the selected widget's settings
  3. Drag handles let you reposition widgets on the grid
  4. Resize handles let you change widget dimensions
  5. Click Apply to save property changes
  6. Click Done to exit edit mode

The editor snaps to the 24-column grid. You can change the widget's title, position, axis labels, and data source directly in the properties panel.


Complete Example

This example creates a process monitoring dashboard with sensor-bound widgets:

setup;

%% Generate data
rng(42);
N = 10000;
t = linspace(0, 86400, N);  % 24 hours

% Machine mode state channel
scMode = StateChannel('machine');
scMode.X = [0, 3600, 7200, 28800, 36000];
scMode.Y = [0, 1,    1,    2,     1    ];

% Temperature sensor
sTemp = Sensor('T-401', 'Name', 'Temperature');
sTemp.X = t;
sTemp.Y = 74 + 3*sin(2*pi*t/3600) + randn(1,N)*1.2;
sTemp.addStateChannel(scMode);
sTemp.addThresholdRule(struct('machine', 1), 78, ...
    'Direction', 'upper', 'Label', 'Hi Warn');
sTemp.addThresholdRule(struct('machine', 1), 85, ...
    'Direction', 'upper', 'Label', 'Hi Alarm');
sTemp.resolve();

% Pressure sensor
sPress = Sensor('P-201', 'Name', 'Pressure');
sPress.X = t;
sPress.Y = 55 + 20*sin(2*pi*t/7200) + randn(1,N)*1.5;
sPress.addThresholdRule(struct(), 65, 'Direction', 'upper', 'Label', 'Hi Warn');
sPress.addThresholdRule(struct(), 70, 'Direction', 'upper', 'Label', 'Hi Alarm');
sPress.resolve();

%% Build dashboard
d = DashboardEngine('Process Monitoring — Line 4');
d.Theme = 'light';
d.LiveInterval = 5;

% Header row: text + numbers + status
d.addWidget('text', 'Title', 'Overview', 'Position', [1 1 4 2], ...
    'Content', 'Line 4 — Shift A', 'FontSize', 16);
d.addWidget('number', 'Title', 'Temperature', 'Position', [5 1 5 2], ...
    'Sensor', sTemp, 'Units', 'degF', 'Format', '%.1f');
d.addWidget('number', 'Title', 'Pressure', 'Position', [10 1 5 2], ...
    'Sensor', sPress, 'Units', 'psi', 'Format', '%.0f');
d.addWidget('status', 'Title', 'Temp', 'Position', [15 1 5 2], ...
    'Sensor', sTemp);
d.addWidget('status', 'Title', 'Press', 'Position', [20 1 5 2], ...
    'Sensor', sPress);

% Plot row: sensor-bound FastSense widgets
d.addWidget('fastsense', 'Position', [1 3 12 8], 'Sensor', sTemp);
d.addWidget('fastsense', 'Position', [13 3 12 8], 'Sensor', sPress);

% Bottom row: gauge + custom plot
d.addWidget('gauge', 'Title', 'Pressure', 'Position', [1 11 8 6], ...
    'Sensor', sPress, 'Range', [0 100], 'Units', 'psi');
d.addWidget('rawaxes', 'Title', 'Temp Distribution', 'Position', [9 11 8 6], ...
    'PlotFcn', @(ax) histogram(ax, sTemp.Y, 50, ...
        'FaceColor', [0.31 0.80 0.64], 'EdgeColor', 'none'));

d.render();

%% Save
d.save(fullfile(tempdir, 'process_dashboard.json'));

See Also

Clone this wiki locally