diff --git a/wiki/Architecture.md b/wiki/Architecture.md index 3f90ff9b..5696d7de 100644 --- a/wiki/Architecture.md +++ b/wiki/Architecture.md @@ -1,156 +1,129 @@ - # Architecture ## Overview - -FastPlot uses a render-once, re-downsample-on-zoom architecture. Instead of pushing millions of points to the GPU, it maintains a lightweight cache and re-downsamples only the visible range on every interaction. +FastPlot uses a render‑once, re‑downsample‑on‑zoom architecture. Instead of pushing millions of points to the GPU, it maintains a lightweight pyramid cache and re‑downsamples only the visible range on every interaction. ## Project Structure - ``` FastPlot/ -├── install.m # Path install + MEX compilation +├── install.m ├── libs/ -│ ├── FastSense/ # Core plotting engine -│ │ ├── FastSense.m # Main class -│ │ ├── FastSenseGrid.m # Dashboard layout -│ │ ├── FastSenseDock.m # Tabbed container -│ │ ├── FastSenseToolbar.m # Interactive toolbar -│ │ ├── FastSenseTheme.m # Theme system -│ │ ├── FastSenseDataStore.m # SQLite-backed chunked storage -│ │ ├── SensorDetailPlot.m # Sensor detail view with state bands -│ │ ├── NavigatorOverlay.m # Minimap zoom navigator -│ │ ├── ConsoleProgressBar.m # Progress indication -│ │ ├── binary_search.m # Binary search utility -│ │ ├── build_mex.m # MEX compilation script -│ │ └── private/ # Internal algorithms + MEX sources -│ ├── SensorThreshold/ # Sensor and threshold system -│ │ ├── Sensor.m -│ │ ├── StateChannel.m -│ │ ├── ThresholdRule.m -│ │ ├── SensorRegistry.m -│ │ ├── ExternalSensorRegistry.m -│ │ └── private/ # Resolution algorithms -│ ├── EventDetection/ # Event detection and viewer +│ ├── FastSense/ # Core plotting engine +│ │ ├── FastSense.m +│ │ ├── FastSenseGrid.m +│ │ ├── FastSenseDock.m +│ │ ├── FastSenseToolbar.m +│ │ ├── FastSenseTheme.m +│ │ ├── FastSenseDataStore.m +│ │ ├── SensorDetailPlot.m +│ │ ├── NavigatorOverlay.m +│ │ ├── ConsoleProgressBar.m +│ │ ├── binary_search.m +│ │ ├── build_mex.m +│ │ ├── mex_stamp.m +│ │ └── private/ # Downsampling engines + MEX sources +│ ├── SensorThreshold/ # Sensor and threshold management +│ │ ├── SensorTag.m +│ │ ├── StateTag.m +│ │ ├── MonitorTag.m +│ │ ├── CompositeTag.m +│ │ ├── DerivedTag.m +│ │ ├── TagRegistry.m +│ │ └── ... +│ ├── EventDetection/ # Event detection and viewer │ │ ├── Event.m -│ │ ├── EventDetector.m +│ │ ├── EventStore.m │ │ ├── EventViewer.m │ │ ├── LiveEventPipeline.m │ │ ├── NotificationService.m -│ │ ├── EventStore.m -│ │ ├── EventConfig.m -│ │ ├── IncrementalEventDetector.m -│ │ ├── DataSource.m # Abstract data source -│ │ ├── MatFileDataSource.m # File-based data source -│ │ ├── MockDataSource.m # Test data generation -│ │ ├── NotificationRule.m # Email notification rules -│ │ └── private/ # Event grouping algorithms -│ ├── Dashboard/ # Dashboard engine (serializable) +│ │ └── ... +│ ├── Dashboard/ # Widget-based dashboard engine │ │ ├── DashboardEngine.m -│ │ ├── DashboardBuilder.m │ │ ├── DashboardLayout.m +│ │ ├── DashboardBuilder.m │ │ ├── DashboardSerializer.m │ │ ├── DashboardTheme.m │ │ ├── DashboardToolbar.m -│ │ ├── DashboardWidget.m # Abstract widget base │ │ ├── FastSenseWidget.m │ │ ├── GaugeWidget.m -│ │ ├── NumberWidget.m │ │ ├── StatusWidget.m -│ │ ├── TextWidget.m -│ │ ├── TableWidget.m -│ │ ├── RawAxesWidget.m -│ │ ├── EventTimelineWidget.m -│ │ ├── GroupWidget.m # Collapsible/tabbed widget groups -│ │ ├── MultiStatusWidget.m # Grid of status indicators -│ │ ├── BarChartWidget.m -│ │ ├── ScatterWidget.m -│ │ ├── HeatmapWidget.m -│ │ ├── HistogramWidget.m -│ │ ├── ImageWidget.m -│ │ └── MarkdownRenderer.m # HTML conversion for info panels -│ └── WebBridge/ # TCP server for web visualization +│ │ ├── ... (many widgets) +│ │ └── GroupWidget.m +│ └── WebBridge/ # TCP server for web visualization │ ├── WebBridge.m │ └── WebBridgeProtocol.m -├── examples/ # 40+ runnable examples -└── tests/ # 30+ test suites +├── examples/ +└── tests/ ``` ## Render Pipeline - -1. User calls `render()` -2. Create figure/axes if not parented -3. Validate all data (X monotonic, dimensions match) -4. Switch to disk storage mode if data exceeds `MemoryLimit` -5. Allocate downsampling buffers based on axes pixel width -6. For each line: initial downsample of full range, create graphics object -7. Create threshold, band, shading, marker objects -8. Install XLim PostSet listener for zoom/pan events -9. Set axis limits, disable auto-limits -10. `drawnow` to display +1. User calls `render()` (or `renderAll()` for grids/dashboards). +2. Create figure/axes if not parented. +3. Validate all data (X monotonic, dimensions match). +4. Optionally switch to disk storage mode if data exceeds `MemoryLimit`. +5. Allocate downsampling buffers sized to axes pixel width. +6. For each line: initial downsample of full range → create graphics object. +7. Create threshold, band, shading, marker objects. +8. Install `XLim` PostSet listener for zoom/pan events. +9. Set axis limits, disable auto-limits. +10. `drawnow` to display; for large datasets, async refinement may be scheduled. ## Zoom/Pan Callback - When the user zooms or pans: - -1. XLim listener fires -2. Compare new XLim to cached value (skip if unchanged) +1. `XLim` listener fires. +2. Compare new XLim to cached value (skip if unchanged). 3. For each line: - - Binary search visible X range — O(log N) - - Select pyramid level with sufficient resolution - - Build pyramid level lazily if needed - - Downsample visible range to ~4,000 points - - Update hLine.XData/YData (dot notation for speed) -4. Recompute violation markers (fused SIMD with pixel culling) -5. If LinkGroup active: propagate XLim to linked plots -6. `drawnow limitrate` (caps display at 20 FPS) + - Binary search visible X range — O(log N) using `binary_search`. A MEX‑accelerated path (`binary_search_mex`) provides 10–20× speedup. + - Select the pyramid level with sufficient resolution (see [[#Lazy-Multi-Resolution-Pyramid]]). + - Build pyramid level lazily if needed. + - Downsample visible range to ~`DownsampleFactor × axes width` points. + - Update `hLine.XData/YData` via dot notation for speed. +4. Recompute violation markers (fused SIMD with pixel culling if MEX available). +5. If `LinkGroup` active: propagate XLim to linked plots (see [[API Reference: FastPlot]]). +6. `drawnow limitrate` (caps display at 20 FPS). ## Downsampling Algorithms - ### MinMax (default) -For each pixel bucket, keep the minimum and maximum Y values. Preserves signal envelope and extreme values. Fast O(N/bucket) per bucket. +For each pixel bucket, keeps the minimum and maximum Y values. Preserves signal envelope and extremes. O(N/bucket) per bucket. ### LTTB (Largest Triangle Three Buckets) -Visually optimal downsampling that preserves signal shape by maximizing triangle area between consecutive buckets. Better visual fidelity but slightly slower. +Visually optimal downsampling that preserves signal shape by maximizing triangle area between consecutive buckets. Better visual fidelity but higher compute cost. -Both algorithms handle NaN gaps by segmenting contiguous non-NaN regions independently. +Both algorithms handle NaN gaps by segmenting contiguous non‑NaN regions independently. -## Lazy Multi-Resolution Pyramid +### Lazy Multi‑Resolution Pyramid +Problem: At full zoom‑out with 50M+ points, scanning all data is O(N). -Problem: At full zoom-out with 50M+ points, scanning all data is O(N). - -Solution: Pre-computed MinMax pyramid with configurable reduction factor (default 100x per level): +Solution: Pre‑computed MinMax pyramid with configurable reduction factor `PyramidReduction` (default 100× per level): ``` -Level 0: Raw data (50,000,000 points) -Level 1: 100x reduction ( 500,000 points) -Level 2: 100x reduction ( 5,000 points) +Level 0: Raw data (50,000,000 pts) +Level 1: 100× reduction ( 500,000 pts) +Level 2: 100× reduction ( 5,000 pts) ``` -On zoom, the coarsest level with sufficient resolution is selected. Full zoom-out reads level 2 (5K points) and downsamples to ~4K in under 1ms. +On zoom, the coarsest level with sufficient resolution is selected. Full zoom‑out reads level 2 (5K pts) and downsamples to ~4K in under 1 ms. -Levels are built lazily on first access — the first zoom-out pays a one-time build cost (~70ms with MEX), subsequent queries are instant. +Levels are built lazily on first access — the first zoom‑out pays a one‑time build cost (~70 ms with MEX). Subsequent queries are instant. ## MEX Acceleration +Optional C MEX functions with SIMD intrinsics (AVX2 on x86_64, NEON on arm64) provide significant speedups: -Optional C MEX functions with SIMD intrinsics (AVX2 on x86_64, NEON on arm64): - -| Function | Speedup | Description | -|----------|---------|-------------| -| binary_search_mex | 10-20x | O(log n) visible range lookup | -| minmax_core_mex | 3-10x | Per-pixel MinMax reduction | -| lttb_core_mex | 10-50x | Triangle area computation | -| violation_cull_mex | significant | Fused detection + pixel culling | -| compute_violations_mex | significant | Batch violation detection for resolve() | -| resolve_disk_mex | significant | SQLite disk-based sensor resolution | -| build_store_mex | 2-3x | Bulk SQLite writer for DataStore init | -| to_step_function_mex | significant | SIMD step-function conversion for thresholds | +| MEX Function | Speedup | Description | +|---------------------------|-----------|--------------------------------------------| +| `binary_search_mex` | 10–20× | O(log n) visible range lookup | +| `minmax_core_mex` | 3–10× | Per‑pixel MinMax reduction | +| `lttb_core_mex` | 10–50× | Triangle area computation | +| `violation_cull_mex` | significant | Fused detection + pixel culling | +| `compute_violations_mex` | significant | Batch violation detection for resolve() | +| `resolve_disk_mex` | significant | SQLite disk‑based sensor resolution | +| `build_store_mex` | 2–3× | Bulk SQLite writer for DataStore init | +| `to_step_function_mex` | significant | SIMD step‑function conversion for thresholds | -All share a common `simd_utils.h` abstraction layer. If MEX is unavailable, pure-MATLAB implementations are used with identical behavior. +All share a common `simd_utils.h` abstraction layer. If MEX is unavailable, pure‑MATLAB implementations are used with identical behavior. See [[MEX Acceleration]] for compilation details. ## Data Flow Architecture - ### Core Data Path ``` Raw Data (X, Y arrays) @@ -159,7 +132,7 @@ FastSenseDataStore (optional, for large datasets) ↓ Downsampling Engine (MinMax/LTTB) ↓ -Pyramid Cache (lazy multi-resolution) +Pyramid Cache (lazy multi‑resolution) ↓ Graphics Objects (line handles) ↓ @@ -167,58 +140,54 @@ Interactive Display ``` ### Storage Modes -- **Memory mode**: X/Y arrays held in MATLAB workspace -- **Disk mode**: Data chunked into SQLite database via `FastSenseDataStore` -- **Auto mode**: Switches to disk when data exceeds `MemoryLimit` (default 500MB) +- **Memory mode**: X/Y arrays held in MATLAB workspace. +- **Disk mode**: Data chunked into SQLite database via `FastSenseDataStore`. +- **Auto mode**: Switches to disk when data exceeds `MemoryLimit` (default 500 MB). ## Sensor Threshold Resolution +The SensorThreshold library uses a segment‑based algorithm for resolving threshold violations. For each sensor: -The `Sensor.resolve()` algorithm is segment-based: - -1. Collect all state-change timestamps from all StateChannels +1. Collect all state‑change timestamps from all `StateChannel`s. 2. For each segment between state changes: - - Evaluate which ThresholdRules match the current state - - Group rules with identical conditions -3. Assign threshold values per segment -4. Detect violations using SIMD-accelerated comparison + - Evaluate which `ThresholdRule`s match the current state. + - Group rules with identical conditions. +3. Assign threshold values per segment. +4. Detect violations using SIMD‑accelerated comparison (MEX). -Complexity: O(S × R) where S = state segments and R = rules, instead of O(N × R) per-point evaluation. +Complexity is O(S × R) where S = state segments and R = rules, instead of O(N × R) per‑point evaluation. -## Disk-Backed Data Storage +## Disk‑Backed Data Storage +For datasets exceeding available memory (100M+ points), `FastSenseDataStore` provides SQLite‑backed chunked storage: -For datasets exceeding available memory (100M+ points), `FastSenseDataStore` provides SQLite-backed chunked storage: +1. Data split into chunks (~10K–500K points each, auto‑tuned). +2. Each chunk stored as typed BLOBs (X and Y) with X range metadata. +3. On zoom/pan, only chunks overlapping the visible range are loaded. +4. Pre‑computed L1 MinMax pyramid for instant zoom‑out. -1. Data is split into chunks (~10K-500K points each, auto-tuned) -2. Each chunk stored as a pair of typed BLOBs (X and Y) with X range metadata -3. On zoom/pan, only chunks overlapping the visible range are loaded -4. Pre-computed L1 MinMax pyramid for instant zoom-out - -The bulk write path uses `build_store_mex` — a single C call that writes all chunks with SIMD-accelerated Y min/max computation, replacing ~20K mksqlite round-trips. +The bulk write path uses `build_store_mex` — a single C call that writes all chunks with SIMD‑accelerated Y min/max computation, replacing ~20K `mksqlite` round‑trips. If SQLite is unavailable, a binary file fallback is used automatically. ## Theme Inheritance - +Themes cascade in this order: ``` Element override > Tile theme > Figure theme > 'default' preset ``` - -Each level fills in only the fields it specifies; unspecified fields cascade from the next level. +Each level fills in only the fields it specifies; unspecified fields cascade from the next level. See [[API Reference: Themes]]. ## Dashboard Architecture +The library provides two dashboard approaches: -### FastSenseGrid vs DashboardEngine +- **`FastSenseGrid`**: Simple tiled grid of [[API Reference: FastPlot|FastSense]] instances with synchronized live mode. See [[API Reference: Dashboard]] for basic usage. -- **[[Dashboard|FastSenseGrid]]**: Simple tiled grid of FastSense instances with synchronized live mode -- **[[Dashboard Engine Guide|DashboardEngine]]**: Full widget-based dashboard with gauges, numbers, status indicators, tables, timelines, and edit mode +- **`DashboardEngine`**: Full widget‑based dashboard with gauges, numbers, status indicators, tables, timelines, and edit mode. Supports multi‑page layouts and JSON serialization. See [[Dashboard Engine Guide]] for a detailed walkthrough. ### DashboardEngine Components - ``` DashboardEngine ├── DashboardToolbar — Top toolbar (Live, Edit, Save, Export, Sync) -├── DashboardLayout — 24-column responsive grid with scrollable canvas -├── DashboardTheme — FastSenseTheme + dashboard-specific fields +├── DashboardLayout — 24‑column responsive grid with scrollable canvas +├── DashboardTheme — FastSenseTheme + dashboard‑specific fields ├── DashboardBuilder — Edit mode overlay (drag/resize, palette, properties) ├── DashboardSerializer — JSON save/load and .m script export └── Widgets (DashboardWidget subclasses) @@ -228,98 +197,87 @@ DashboardEngine ├── StatusWidget — Colored dot indicator ├── TextWidget — Static label or header ├── TableWidget — uitable display - ├── RawAxesWidget — User-supplied plot function + ├── RawAxesWidget — User‑supplied plot function ├── EventTimelineWidget — Colored event bars on timeline ├── GroupWidget — Collapsible panels, tabbed containers └── MultiStatusWidget — Grid of sensor status dots ``` ### Render Flow - -1. `DashboardEngine.render()` creates the figure -2. `DashboardTheme(preset)` generates the full theme struct -3. `DashboardToolbar` creates the top toolbar panel -4. Time control panel (dual sliders) is created at the bottom -5. `DashboardLayout.createPanels()` computes grid positions, creates viewport/canvas/scrollbar, and creates a uipanel per widget -6. Each widget's `render(parentPanel)` is called to populate its panel -7. `updateGlobalTimeRange()` scans widgets for data bounds and configures the time sliders +1. `DashboardEngine.render()` creates the figure. +2. `DashboardTheme(preset)` generates the full theme struct. +3. `DashboardToolbar` creates the top toolbar panel. +4. Time control panel (dual sliders) is created at the bottom. +5. `DashboardLayout.createPanels()` computes grid positions, creates viewport/canvas/scrollbar, and creates a uipanel per widget. +6. Each widget's `render(parentPanel)` is called to populate its panel. +7. `updateGlobalTimeRange()` scans widgets for data bounds and configures the time sliders. ### Live Mode - When `startLive()` is called, a timer fires at `LiveInterval` seconds: -1. `updateLiveTimeRange()` expands time bounds from new data -2. Each widget's `refresh()` is called (sensor-bound widgets re-read `Sensor.Y(end)`) -3. The toolbar timestamp label is updated -4. Current slider positions are re-applied to the updated time range +1. `updateLiveTimeRange()` expands time bounds from new data. +2. Each widget's `refresh()` is called. +3. The toolbar timestamp label is updated. +4. Current slider positions are re‑applied to the updated time range. ### Edit Mode - -Clicking "Edit" in the toolbar creates a `DashboardBuilder` instance: -1. A palette sidebar (left) shows widget type buttons -2. A properties panel (right) shows selected widget settings -3. Drag/resize overlays are added on top of each widget panel -4. The content area narrows to accommodate sidebars -5. Mouse move/up callbacks handle drag and resize interactions -6. Grid snap rounds positions to the nearest column/row +Clicking “Edit” in the toolbar creates a `DashboardBuilder` instance: +1. A palette sidebar (left) shows widget type buttons. +2. A properties panel (right) shows selected widget settings. +3. Drag/resize overlays are added on top of each widget panel. +4. The content area narrows to accommodate sidebars. +5. Mouse move/up callbacks handle drag and resize interactions. +6. Grid snap rounds positions to the nearest column/row. ### JSON Persistence - -`DashboardSerializer` handles round-trip serialization: -- **Save:** each widget's `toStruct()` produces a plain struct with type, title, position, and source. The struct is encoded to JSON with heterogeneous widget arrays assembled manually (MATLAB's `jsonencode` cannot handle cell arrays of mixed structs). -- **Load:** JSON is decoded, widgets array is normalized to cell, and `configToWidgets()` dispatches to each widget class's `fromStruct()` static method. An optional `SensorResolver` function handle re-binds Sensor objects by name. +`DashboardSerializer` handles round‑trip serialization: +- **Save:** each widget’s `toStruct()` produces a plain struct. The struct is encoded to JSON with heterogeneous widget arrays assembled manually (MATLAB’s `jsonencode` cannot handle cell arrays of mixed structs). +- **Load:** JSON is decoded, widgets array is normalized to cell, and `configToWidgets()` dispatches to each widget class’s `fromStruct()` static method. An optional `SensorResolver` function handle re‑binds Sensor objects by name. - **Export script:** generates a `.m` file with `DashboardEngine` constructor calls and `addWidget` calls for each widget. ## Event Detection Architecture - -The event detection system provides real-time threshold violation monitoring with configurable notifications and data persistence. +The event detection system provides real‑time threshold violation monitoring with configurable notifications and data persistence. ### Core Components - ``` LiveEventPipeline -├── DataSourceMap — Maps sensor keys to data sources -├── IncrementalEventDetector — Tracks per-sensor state and open events -├── EventStore — Thread-safe .mat file persistence -├── NotificationService — Rule-based email alerts +├── MonitorTargets — containers.Map of key → MonitorTag +├── DataSourceMap — Maps sensor keys to data sources +├── EventStore — Thread‑safe .mat file persistence +├── NotificationService — Rule‑based email alerts └── EventViewer — Interactive Gantt chart + filterable table ``` -### Data Sources - -- **MatFileDataSource**: Polls .mat files for new data -- **MockDataSource**: Generates realistic test signals with violations -- **Custom sources**: Implement `DataSource.fetchNew()` interface - ### Event Detection Flow - -1. `LiveEventPipeline.runCycle()` polls all data sources -2. New data is passed to `IncrementalEventDetector.process()` -3. Sensor state is evaluated via `Sensor.resolve()` -4. Violations are grouped into events with debouncing (`MinDuration`) -5. Events are stored via `EventStore.append()` (atomic .mat writes) -6. `NotificationService` sends rule-based email alerts with plot snapshots -7. Active `EventViewer` instances auto-refresh to show new events +1. `LiveEventPipeline.runCycle()` polls all data sources. +2. New data is fed to `MonitorTag.appendData()`. +3. `MonitorTag.ConditionFn` evaluates violations. +4. Violations are grouped into events with debouncing (`MinDuration`). +5. Events are stored via `EventStore.append()` (atomic .mat writes). +6. `NotificationService` sends rule‑based email alerts with plot snapshots. +7. Active `EventViewer` instances auto‑refresh to show new events. ### Escalation Logic - When `EscalateSeverity` is enabled, events are promoted to the highest violated threshold: -- A violation starts at "Warning" level -- If "Alarm" threshold is also crossed, the event is escalated to "Alarm" -- The event retains the highest severity level encountered +- A violation starts at “Warning” level. +- If “Alarm” threshold is also crossed, the event is escalated to “Alarm”. +- The event retains the highest severity level encountered. -## Progress Indication +See [[API Reference: Event Detection]] for further details. +## Progress Indication `ConsoleProgressBar` provides hierarchical progress feedback: -- Single-line ASCII/Unicode bars with backspace-based updates -- Indentation support for nested operations (e.g., dock → tabs → tiles) -- Freeze/finish modes for permanent status lines +- Single‑line ASCII/Unicode bars with backspace‑based updates. +- Indentation support for nested operations (e.g., dock → tabs → tiles). +- Freeze/finish modes for permanent status lines. ## Interactive Features - ### Toolbars and Navigation -- **[[API Reference: FastPlot|FastSenseToolbar]]**: Data cursor, crosshair, grid toggle, autoscale, export, live mode -- **DashboardToolbar**: Live toggle, edit mode, save/export, name editing -- **NavigatorOverlay**: Minimap with draggable zoom rectangle for `SensorDetailPlot` +- **[[API Reference: FastPlot|FastSenseToolbar]]**: Data cursor, crosshair, grid/legend toggle, autoscale, export, live mode. +- **DashboardToolbar**: Live toggle, edit mode, save/export, name editing. +- **NavigatorOverlay**: Minimap with draggable zoom rectangle for `SensorDetailPlot`. ### Link Groups -Multiple FastSense instances can share synchronized zoom/pan via `LinkGroup` strings. When one plot's XLim changes, all plots in the same group update automatically. +Multiple FastSense instances can share synchronized zoom/pan via `LinkGroup` strings. When one plot’s XLim changes, all plots in the same group update automatically. + +### Hover Crosshair +Enabled by default (`FastSense.HoverCrosshair = true`), it creates a vertical crosshair tracking the cursor and shows interpolated values for all visible lines. See [[API Reference: FastPlot]]. diff --git a/wiki/Dashboard-Engine-Guide.md b/wiki/Dashboard-Engine-Guide.md index 2fff83a1..197a3466 100644 --- a/wiki/Dashboard-Engine-Guide.md +++ b/wiki/Dashboard-Engine-Guide.md @@ -2,7 +2,7 @@ # Dashboard Engine Guide -Build rich, interactive dashboards with mixed widget types, sensor bindings, JSON persistence, and a visual editor. +Build rich, interactive dashboards with mixed widget types, sensor bindings, JSON persistence, multi-page layouts, and a visual editor. --- @@ -12,18 +12,20 @@ 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 | +| Grid | Fixed rows × cols | 24-column responsive | +| Tile content | FastSense instances only | 19 widget types (plots, gauges, KPIs, tables, timelines, bar charts, heatmaps, images, …) | +| Grouping | No | Collapsible panels, tabbed containers | +| Multi-page | No | Named pages with switching | +| 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) | +| 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. +**When to use DashboardEngine:** You need mixed widget types, grouping, multi-page layouts, JSON persistence, or the visual editor. --- @@ -61,9 +63,9 @@ DashboardEngine uses a **24-column grid**. Widget positions are specified as: Position = [col, row, width, height] ``` -- `col`: column (1-24), left to right +- `col`: column (1–24), left to right - `row`: row (1+), top to bottom -- `width`: number of columns to span (1-24) +- `width`: number of columns to span (1–24) - `height`: number of rows to span Examples: @@ -78,8 +80,50 @@ If a new widget overlaps an existing one, it is automatically pushed down to the --- +## 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: + +```matlab +% Create and configure sensor +sTemp = Sensor('T-401', 'Name', 'Temperature'); +sTemp.Units = 'degF'; +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, status, sparkline, and iconcards +- **Thresholds:** FastSenseWidget renders resolved thresholds and violations; StatusWidget and IconCardWidget derive state color automatically +- **Sparkline data:** SparklineCardWidget uses the sensor history for its mini‑chart +- **Live refresh:** calling `refresh()` re-reads the sensor data + +You can also bind widgets to a Tag (v2.0 API) directly, but the `'Sensor'` syntax is retained for backward compatibility. + +--- + ## Widget Types +DashboardEngine supports 19 widget types. All can be added via `d.addWidget(type, 'Property', value, ...)`, or by constructing the widget object and passing it to `addWidget`. + ### FastSense (time series) ```matlab @@ -119,7 +163,7 @@ d.addWidget('number', 'Title', 'CPU Load', ... 'ValueFcn', @() getCpuLoad(), 'Units', '%', 'Format', '%.0f'); ``` -Shows a large number with a trend arrow (up/down/flat) computed from recent sensor data. Layout: `[Title | Value+Trend | Units]`. +Shows a large number with a trend arrow (up/down/flat) computed from recent sensor data. ### Status (health indicator) @@ -132,9 +176,14 @@ d.addWidget('status', 'Title', 'Pump', ... d.addWidget('status', 'Title', 'System', ... 'Position', [12 1 5 2], ... 'StaticStatus', 'ok'); % 'ok', 'warning', 'alarm' + +% Threshold‑bound (no Sensor required) +d.addWidget('status', 'Title', 'Temp', ... + 'Position', [17 1 5 2], ... + 'Threshold', myThreshold, 'ValueFcn', @() readTemp()); ``` -Shows a colored dot (green/amber/red) and the sensor's latest value. Status is derived automatically from threshold rules. +Shows a colored dot (green/amber/red) and the current value. Status is derived automatically from threshold rules or a static string. ### Gauge (arc/donut/bar/thermometer) @@ -149,11 +198,15 @@ d.addWidget('gauge', 'Title', 'Efficiency', ... 'Position', [9 3 8 6], ... 'StaticValue', 85, 'Range', [0 100], 'Units', '%', ... 'Style', 'arc'); -``` -Styles: `'arc'` (default), `'donut'`, `'bar'`, `'thermometer'`. +% Thermometer style +d.addWidget('gauge', 'Title', 'Level', ... + 'Position', [17 3 8 6], ... + 'Sensor', sLevel, 'Range', [0 100], ... + 'Style', 'thermometer'); +``` -When Sensor-bound, range and units are auto-derived from threshold rules and sensor properties. +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) @@ -214,7 +267,6 @@ The `PlotFcn` receives MATLAB axes as the first argument. When Sensor-bound, it % 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); @@ -231,100 +283,184 @@ d.addWidget('timeline', 'Title', 'Temp Events', ... 'FilterSensors', {'T-401', 'T-402'}); ``` ---- +### Bar Chart -## Sensor Binding +```matlab +d.addWidget('barchart', 'Title', 'Production', ... + 'Position', [1 1 12 6], ... + 'DataFcn', @() struct('categories', {{'A','B','C'}}, ... + 'values', [120, 95, 150]), ... + 'Orientation', 'vertical', 'Stacked', false); +``` -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: +The callback must return a struct with fields `categories` (cell array of strings) and `values` (numeric vector). Use `'Orientation', 'horizontal'` for horizontal bars. + +### Chip Bar (multi‑sensor health summary) ```matlab -% Create and configure sensor -sTemp = Sensor('T-401', 'Name', 'Temperature'); -sTemp.Units = 'degF'; -sTemp.X = t; -sTemp.Y = temp; +d.addWidget('chipbar', 'Title', 'System Health', ... + 'Position', [1 1 24 1], ... + 'Chips', { ... + struct('label', 'Pump', 'statusFcn', @() 'ok'), ... + struct('label', 'Tank', 'sensor', sTank), ... + struct('label', 'Fan', 'statusFcn', @() 'warn') ... + }); +``` -sc = StateChannel('machine'); -sc.X = [0 7200 43200]; sc.Y = [0 1 0]; -sTemp.addStateChannel(sc); +Displays a compact horizontal row of colored status circles, ideal for system‑health overviews. Each chip can derive color from a sensor’s threshold state, a status callback, or a static RGB. -sTemp.addThresholdRule(struct('machine', 1), 78, ... - 'Direction', 'upper', 'Label', 'Hi Warn'); -sTemp.addThresholdRule(struct('machine', 1), 85, ... - 'Direction', 'upper', 'Label', 'Hi Alarm'); -sTemp.resolve(); +### Divider (horizontal line) -% 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]); +```matlab +d.addWidget('divider', 'Position', [1 3 24 1], 'Thickness', 2, 'Color', [0.7 0.7 0.7]); ``` -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 +Use to visually separate dashboard sections. Thickness goes from 1 (thin) to 3 (thick). ---- +### Group (panel, collapsible, tabbed) -## Saving and Loading +Group widgets let you nest child widgets. -### Save to JSON +```matlab +% Simple panel +w = GroupWidget('Label', 'Process Overview', 'Mode', 'panel'); +w.Children = {w1, w2, w3}; +d.addWidget(w); + +% Collapsible group with auto‑flow children +d.addCollapsible('Sensor Details', {w1, w2}, 'Position', [1 1 6 4]); + +% Tabbed group +gt = GroupWidget('Label', 'Details', 'Mode', 'tabbed'); +gt.Tabs = { ... + struct('name', 'Temp', 'widgets', {{w_temp1, w_temp2}}), ... + struct('name', 'Press', 'widgets', {{w_press1}}) }; +d.addWidget(gt, 'Position', [1 1 12 6]); +``` + +The collapsible shortcut method `addCollapsible` creates a `GroupWidget` with `Mode='collapsible'` and auto‑positions child widgets. Tabbed groups display one tab at a time. + +### Heatmap ```matlab -d.save('dashboard.json'); +d.addWidget('heatmap', 'Title', 'Correlation', ... + 'Position', [1 1 6 6], ... + 'DataFcn', @() corrMat, ... + 'Colormap', 'parula', 'ShowColorbar', true); +``` + +### Histogram + +```matlab +d.addWidget('histogram', 'Title', 'Temp Distribution', ... + 'Position', [1 1 6 4], ... + 'DataFcn', @() tempData, ... + 'NumBins', 30, 'ShowNormalFit', true); +``` + +### IconCard (Mushroom‑style card) + +```matlab +% Sensor‑bound – state color automatically derived +d.addWidget('iconcard', 'Title', 'Pump', ... + 'Position', [1 1 4 2], ... + 'Sensor', sPump, 'Units', 'rpm', 'Format', '%.0f'); + +% Static state +d.addWidget('iconcard', 'Title', 'Motor', ... + 'Position', [5 1 4 2], ... + 'StaticValue', 0, 'StaticState', 'inactive', 'SecondaryLabel', 'Off'); ``` -The JSON file contains the dashboard name, theme, live interval, grid settings, and each widget's type, title, position, and data source. +Displays a colored icon circle, a large primary value, and a subtitle label. Icon color reflects the current threshold state (ok/warn/alarm/info/inactive). -### Load from JSON +### Image ```matlab -d2 = DashboardEngine.load('dashboard.json'); -d2.render(); +d.addWidget('image', 'Title', 'Site Layout', ... + 'Position', [1 1 8 6], ... + 'File', 'layout.png', 'Scaling', 'fit', 'Caption', 'Floor 2'); ``` -To re-bind Sensor objects on load, provide a resolver function: +### MultiStatus (grid of status dots) ```matlab -d2 = DashboardEngine.load('dashboard.json', ... - 'SensorResolver', @(name) SensorRegistry.get(name)); -d2.render(); +d.addWidget('multistatus', 'Title', 'Pumps', ... + 'Position', [1 1 6 3], ... + 'Sensors', {sPump1, sPump2, sPump3}, ... + 'ShowLabels', true, 'IconStyle', 'square'); ``` -### Export as MATLAB Script +### Scatter (sensor vs sensor) ```matlab -d.exportScript('rebuild_dashboard.m'); +d.addWidget('scatter', 'Title', 'Temp vs Load', ... + 'Position', [1 1 8 6], ... + 'SensorX', sTemp, 'SensorY', sLoad, ... + 'MarkerSize', 6, 'Colormap', 'parula'); +``` + +Optionally color‑code points with a third sensor via `SensorColor`. + +### SparklineCard (KPI with mini‑chart) + +```matlab +d.addWidget('sparklinecard', 'Title', 'CPU Load', ... + 'Position', [1 1 4 2], ... + 'StaticValue', 42.5, 'SparkData', cpuHistory, ... + 'Units', '%', 'ShowDelta', true, 'DeltaFormat', '%+.1f'); ``` -Generates a readable `.m` file with `DashboardEngine` constructor and `addWidget` calls that recreates the dashboard. +Sensor‑bound: the sparkline shows the sensor’s last N points, the value is the latest reading, and the delta is derived from the slope. --- -## Theming +## Grouping Widgets -DashboardEngine uses `DashboardTheme`, which extends `FastSenseTheme` with dashboard-specific fields (widget backgrounds, border colors, status indicator colors, etc.). +You can organize widgets into collapsible panels or tabbed containers using the `GroupWidget` class. + +**Collapsible groups** are created with `addCollapsible`: ```matlab -d = DashboardEngine('My Dashboard'); -d.Theme = 'dark'; % or 'light', 'industrial', 'scientific', 'ocean' -d.render(); +w1 = NumberWidget('Title', 'Temp', ...); +w2 = NumberWidget('Title', 'Pressure', ...); +d.addCollapsible('Sensor KPIs', {w1, w2}, ... + 'Position', [1 1 12 4], 'Collapsed', false); ``` -Available presets: `'default'`, `'dark'`, `'light'`, `'industrial'`, `'scientific'`, `'ocean'`. +This automatically arranges the children within the group’s grid area. Set `ChildAutoFlow = true` (default) to let the group auto‑position children. -You can also override specific theme properties: +**Tabbed groups** allow switching between sets of widgets: ```matlab -theme = DashboardTheme('dark', 'WidgetBackground', [0.1 0.1 0.2]); -d.Theme = theme; +gt = GroupWidget('Label', 'Details', 'Mode', 'tabbed'); +gt.Tabs = { ... + struct('name', 'Temp', 'widgets', {{w_temp1, w_temp2}}), ... + struct('name', 'Press', 'widgets', {{w_press1}}) }; +d.addWidget(gt, 'Position', [1 1 12 6]); ``` +Call `gt.switchTab('Press')` programmatically, or let the user click the tab headers. + +--- + +## Multi‑Page Dashboards + +Large dashboards can be split into named pages with a page bar for switching: + +```matlab +d = DashboardEngine('Plant Monitor'); +d.addPage('Overview'); % now the active page +d.addWidget('fastsense', ...); % goes to Overview + +d.addPage('Details'); % switches to Details page +d.addWidget('table', ...); + +d.render(); % renders the active page with a page bar +``` + +Switch pages programmatically: `d.switchPage(2)` (index). The toolbar includes a page bar when pages exist. + --- ## Live Mode @@ -337,7 +473,6 @@ 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 @@ -345,7 +480,9 @@ d.startLive(); % start periodic refresh d.stopLive(); % stop ``` -You can also toggle live mode from the toolbar's Live button. The toolbar shows the last update timestamp when live mode is active. +You can also toggle live mode from the toolbar’s **Live** button. The toolbar shows the last update timestamp when live mode is active. + +**Stale data detection:** If a widget’s time range hasn’t advanced on a live tick, a banner warns the user. The banner can be dismissed, and it stays hidden until data resumes. --- @@ -356,8 +493,9 @@ The time panel at the bottom of the dashboard has two sliders that control the v - **FastSenseWidget:** sets xlim on the FastSense axes - **EventTimelineWidget:** sets xlim on the timeline axes - **RawAxesWidget:** passes the time range to the PlotFcn +- **GaugeWidget & others:** unaffected unless they implement time control -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. +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. --- @@ -366,19 +504,88 @@ If a user manually zooms a specific widget, that widget detaches from global tim 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 +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. +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. -Widget management functions: -- `addWidget(type)` - add a new widget of the specified type -- `deleteWidget(idx)` - remove widget by index -- `selectWidget(idx)` - select a widget for property editing -- `setWidgetPosition(idx, pos)` - move/resize widget programmatically +Programmatic widget management functions: +- `d.addWidget(type)` — add a new widget of the specified type +- `d.removeWidget(idx)` — remove widget by index +- `d.setWidgetPosition(idx, pos)` — move/resize widget programmatically +- `d.getWidgetByTitle(title)` — find a widget by its Title + +--- + +## Saving and Loading + +### Save to MATLAB function (recommended) + +```matlab +d.save('dashboard.json'); +``` + +This generates a `.m` function that returns a `DashboardEngine` instance. The file can be loaded with: + +```matlab +d2 = DashboardEngine.load('dashboard.m'); +d2.render(); +``` + +### Save to JSON + +```matlab +d.saveJSON('dashboard.json'); +``` + +Load back with sensor resolution: + +```matlab +d2 = DashboardEngine.load('dashboard.json', ... + 'SensorResolver', @(name) SensorRegistry.get(name)); +d2.render(); +``` + +### Export as standalone script + +```matlab +d.exportScript('rebuild_dashboard.m'); +``` + +Generates a readable `.m` file that reconstructs the dashboard using `DashboardEngine` constructor and `addWidget` calls. + +### Export image (PNG/JPEG) + +```matlab +d.exportImage('dashboard.png'); % PNG at 150 DPI +d.exportImage('dashboard.jpg', 'jpeg'); % JPEG +``` + +--- + +## Theming + +DashboardEngine uses `DashboardTheme`, which extends `FastSenseTheme` with dashboard-specific fields (widget backgrounds, border colors, status indicator colors, etc.). + +```matlab +d = DashboardEngine('My Dashboard'); +d.Theme = 'dark'; % or 'light' +d.render(); +``` + +**Available presets:** `'light'` (default) and `'dark'`. Legacy preset names (`'default'`, `'industrial'`, `'scientific'`, `'ocean'`) are aliased to `'light'` for backward compatibility. + +You can override specific theme properties: + +```matlab +theme = DashboardTheme('dark', 'WidgetBackground', [0.1 0.1 0.2], 'KpiFontSize', 32); +d.Theme = theme; +``` + +Theme changes propagate to the toolbar, time panel, and all widgets. --- @@ -392,13 +599,13 @@ d.InfoFile = 'dashboard_help.md'; % path to Markdown file d.render(); ``` -An **Info** button appears in the toolbar. Clicking it renders the Markdown file as HTML and opens it in the system browser. Supports basic Markdown syntax including headers, lists, code blocks, and tables. +An **Info** button appears in the toolbar. Clicking it renders the Markdown file as HTML and opens it in the system browser. Supports basic Markdown syntax including headers, lists, code blocks, tables, and images. --- ## Complete Example -This example creates a process monitoring dashboard with sensor-bound widgets: +This example creates a process monitoring dashboard with sensor-bound widgets, groups, and pages: ```matlab install; @@ -408,26 +615,15 @@ 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.Units = 'degF'; +% Create sensors +sTemp = Sensor('T-401', 'Name', 'Temperature', 'Units', 'degF'); 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.addThresholdRule(struct(), 78, 'Direction', 'upper', 'Label', 'Hi Warn'); +sTemp.addThresholdRule(struct(), 85, 'Direction', 'upper', 'Label', 'Hi Alarm'); sTemp.resolve(); -% Pressure sensor -sPress = Sensor('P-201', 'Name', 'Pressure'); -sPress.Units = 'psi'; +sPress = Sensor('P-201', 'Name', 'Pressure', 'Units', 'psi'); 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'); @@ -438,41 +634,54 @@ sPress.resolve(); d = DashboardEngine('Process Monitoring — Line 4'); d.Theme = 'light'; d.LiveInterval = 5; +d.InfoFile = 'process_help.md'; -% 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], ... +% Page 1: Overview +d.addPage('Overview'); + +% Chip bar health summary +d.addWidget('chipbar', 'Position', [1 1 24 1], 'Chips', { ... + struct('label', 'Temp', 'sensor', sTemp), ... + struct('label', 'Press', 'sensor', sPress) }); + +% Number widgets +d.addWidget('number', 'Title', 'Temperature', 'Position', [1 2 6 2], ... 'Sensor', sTemp, 'Format', '%.1f'); -d.addWidget('number', 'Title', 'Pressure', 'Position', [10 1 5 2], ... +d.addWidget('number', 'Title', 'Pressure', 'Position', [7 2 6 2], ... 'Sensor', sPress, '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')); + +% Collapsible group with gauges +wGauge1 = GaugeWidget('Sensor', sTemp, 'Style', 'donut', ... + 'Range', [74 90], 'Units', 'degF'); +wGauge2 = GaugeWidget('Sensor', sPress, 'Style', 'arc', ... + 'Range', [30 100], 'Units', 'psi'); +d.addCollapsible('Gauges', {wGauge1, wGauge2}, 'Position', [1 4 12 6]); + +% Sparkline cards +d.addWidget('sparklinecard', 'Title', 'Temp Trend', 'Position', [13 4 6 3], ... + 'Sensor', sTemp, 'Units', 'degF', 'ShowDelta', true); +d.addWidget('sparklinecard', 'Title', 'Press Trend', 'Position', [19 4 6 3], ... + 'Sensor', sPress, 'Units', 'psi', 'ShowDelta', true); + +% Page 2: Trends +d.addPage('Trends'); +d.addWidget('fastsense', 'Position', [1 1 12 8], 'Sensor', sTemp); +d.addWidget('fastsense', 'Position', [13 1 12 8], 'Sensor', sPress); + +% Event timeline from EventStore (requires event detection setup) +% d.addWidget('timeline', 'Position', [1 9 24 3], 'EventStoreObj', myEventStore); d.render(); %% Save -d.save(fullfile(tempdir, 'process_dashboard.json')); +d.save(fullfile(tempdir, 'process_dashboard.m')); ``` --- ## See Also -- [[API Reference: Dashboard]] -- Full API reference for all dashboard classes -- [[API Reference: Sensors]] -- Sensor, StateChannel, ThresholdRule -- [[Live Mode Guide]] -- Live data polling -- [[Examples]] -- `example_dashboard_engine`, `example_dashboard_all_widgets` +- [[API Reference: Dashboard]] — Full API reference for all dashboard classes +- [[API Reference: Sensors]] — Sensor, StateChannel, ThresholdRule +- [[Live Mode Guide]] — Live data polling details +- [[Examples]] — Example scripts: `example_dashboard_engine`, `example_dashboard_all_widgets`