diff --git a/wiki/Architecture.md b/wiki/Architecture.md index 3f90ff9b..6a13e7cf 100644 --- a/wiki/Architecture.md +++ b/wiki/Architecture.md @@ -4,322 +4,353 @@ ## 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 multi‑resolution pyramid cache and re‑downsamples only the visible range on every interaction. This approach keeps zoom/pan fluid even for datasets with 100M+ samples. + +The core rendering engine is **FastSense**, which provides high‑performance time‑series plots. Higher‑level components include **FastSenseGrid** (tiled dashboards), **DashboardEngine** (widget‑based dashboards with gauges, numbers, event timelines), and the **Event Detection** pipeline. A unified **Tag** domain model (v2.0) replaces legacy Sensor/Threshold classes, offering a consistent abstraction for raw sensors, discrete states, monitors, composites, and derived signals. ## Project Structure ``` FastPlot/ -├── install.m # Path install + MEX compilation +├── install.m # Path setup + MEX compilation ├── 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 -│ │ ├── Event.m -│ │ ├── EventDetector.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) -│ │ ├── DashboardEngine.m -│ │ ├── DashboardBuilder.m -│ │ ├── DashboardLayout.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 +│ ├── FastSense/ # Core plotting engine +│ │ ├── FastSense.m # Main class +│ │ ├── FastSenseGrid.m # Tiled 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 navigator +│ │ ├── NavigatorOverlay.m # Minimap zoom rectangle +│ │ ├── ConsoleProgressBar.m # Hierarchical progress bars +│ │ ├── HoverCrosshair.m # Hover-driven crosshair + datatip +│ │ ├── binary_search.m # Binary search utility (MEX‑accelerated) +│ │ ├── build_mex.m # MEX compilation script +│ │ ├── mex_stamp.m # Fingerprint for MEX cache validation +│ │ └── private/ # Internal algorithms + MEX sources +│ ├── SensorThreshold/ # Tag domain model + registry +│ │ ├── Tag.m # Abstract base +│ │ ├── SensorTag.m # Raw sensor data +│ │ ├── StateTag.m # Discrete state signals (ZOH) +│ │ ├── MonitorTag.m # Binary 0/1 monitor (lazy) +│ │ ├── CompositeTag.m # Multi‑child aggregator +│ │ ├── DerivedTag.m # Continuous derived signal +│ │ ├── TagRegistry.m # Singleton catalog +│ │ ├── BatchTagPipeline.m # Bulk raw‑data → per‑tag .mat +│ │ ├── LiveTagPipeline.m # Timer‑driven raw‑data → .mat +│ │ └── private/ # Resolution / parse helpers +│ ├── EventDetection/ # Event detection and viewer +│ │ ├── EventStore.m # Thread‑safe .mat persistence +│ │ ├── Event.m # Event handle +│ │ ├── EventBinding.m # Many‑to‑many Event↔Tag registry +│ │ ├── LiveEventPipeline.m # Monitor‑based live detection +│ │ ├── EventViewer.m # Gantt + table GUI +│ │ ├── NotificationService.m # Email alerts with snapshots +│ │ ├── DataSource.m # Abstract data source +│ │ ├── MatFileDataSource.m # File‑based polling +│ │ ├── MockDataSource.m # Test data generator +│ │ ├── NotificationRule.m # Per‑sensor/threshold notification rules +│ │ └── private/ # Internal utilities +│ ├── Dashboard/ # Widget‑based dashboard engine +│ │ ├── DashboardEngine.m # Top‑level orchestrator +│ │ ├── DashboardLayout.m # 24‑column responsive grid + scrolling +│ │ ├── DashboardTheme.m # Dashboard‑specific theme fields +│ │ ├── DashboardToolbar.m # Toolbar with Live, Export, Config +│ │ ├── DashboardBuilder.m # Edit‑mode drag/resize + palette +│ │ ├── DashboardSerializer.m # JSON save/load + .m script export +│ │ ├── DashboardPage.m # Multi‑page container +│ │ ├── DashboardProgress.m # Render progress feedback +│ │ ├── DashboardWidget.m # Abstract widget base +│ │ ├── FastSenseWidget.m # FastSense chart wrapper +│ │ ├── GaugeWidget.m # Arc/donut/bar/thermometer gauge +│ │ ├── NumberWidget.m # Big number + trend arrow +│ │ ├── StatusWidget.m # Colored dot indicator +│ │ ├── TextWidget.m # Static label / header +│ │ ├── TableWidget.m # uitable display +│ │ ├── RawAxesWidget.m # User‑supplied plot function +│ │ ├── EventTimelineWidget.m # Colored event bars on timeline +│ │ ├── GroupWidget.m # Collapsible panels / tabs +│ │ ├── MultiStatusWidget.m # Grid of sensor status dots +│ │ ├── IconCardWidget.m # Mushroom‑style icon + value + label +│ │ ├── ChipBarWidget.m # Compact row of status chips +│ │ ├── SparklineCardWidget.m # Big number + sparkline + delta +│ │ ├── DividerWidget.m # Horizontal separator +│ │ ├── BarChartWidget.m # Bar chart via raw axes +│ │ ├── ScatterWidget.m # Scatter plot +│ │ ├── HeatmapWidget.m # 2‑D color grid +│ │ ├── HistogramWidget.m # Histogram +│ │ ├── ImageWidget.m # Embedded image +│ │ ├── MarkdownRenderer.m # Markdown‑to‑HTML converter +│ │ ├── TimeRangeSelector.m # Dual‑slider time scrubber +│ │ └── DetachedMirror.m # Live mirror in standalone figure +│ └── WebBridge/ # TCP server for web visualization │ ├── WebBridge.m │ └── WebBridgeProtocol.m -├── examples/ # 40+ runnable examples -└── tests/ # 30+ test suites +├── examples/ # 40+ runnable examples +└── tests/ # 30+ test suites ``` ## 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 +The core render sequence in **FastSense** (and [FastSenseWidget]()) follows these steps: + +1. User calls `render()` (or `renderAll()` for grids). +2. Create figure and axes if no parent is provided. +3. Validate all input: X monotonic, dimensions match. +4. If total data size exceeds `MemoryLimit`, switch to disk-based storage (`FastSenseDataStore`). +5. Compute the number of visible pixels and allocate downsampling buffers accordingly. +6. **Initial downsample** of the full X-range for each line, using MinMax or LTTB, yielding ~4 000 points. +7. Create graphics objects (lines, bands, shadings, threshold lines, markers). +8. Install a `XLim` `PostSet` listener to catch zoom/pan events. +9. Set initial axis limits (5% padding), disable auto‑limits. +10. Execute `drawnow` to display. + +For **DashboardEngine**, the render flow is: + +1. `DashboardEngine.render()` creates the figure. +2. `DashboardTheme(preset)` generates the full theme struct. +3. `DashboardToolbar` and (optionally) a multi‑page tab bar and time slider panel are created. +4. `DashboardLayout.allocatePanels()` computes grid positions, creates viewport/canvas/scrollbar, and pre‑allocates a `uipanel` for each widget. +5. Each widget’s `render(parentPanel)` populates its panel (e.g., `FastSenseWidget` creates a `FastSense` inside it). +6. `updateGlobalTimeRange()` scans widgets for data bounds and configures the dual time sliders. ## Zoom/Pan Callback -When the user zooms or pans: +Whenever the user zooms or pans: -1. XLim listener fires -2. Compare new XLim to cached value (skip if unchanged) +1. The `XLim` listener fires. +2. Compare new limits to cached values; 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) + - Perform a binary search (O(log N)) to find the visible X indices. + - Select the coarsest pyramid level that still provides sufficient resolution. + - If the level does not exist, build it lazily. + - Downsample the visible range to ≈4 000 screen points. + - Update the line’s `XData`/`YData` using dot‑notation (fast assignment). +4. Recompute threshold violation markers and event‑layer markers (if enabled). +5. If `LinkGroup` is active, propagate the new `XLim` to all linked plots. +6. Call `drawnow limitrate` to cap the update rate at ~20 FPS. + +Downsampling and pyramid‑level selection keep the per‑frame cost essentially independent of the total dataset size. ## 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, keep the minimum **and** maximum Y values. This preserves the signal envelope and extreme spikes. Complexity is O(N / bucket_width) 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. +A visually optimal technique that selects one representative point per bucket by maximizing the triangle area formed with the previous and next bucket’s chosen point. Provides better visual fidelity for smooth curves, but slower than MinMax. -Both algorithms handle NaN gaps by segmenting contiguous non-NaN regions independently. +Both algorithms handle NaN gaps by processing contiguous non‑NaN segments independently. + +> **MEX acceleration** boosts MinMax by 3‑10× and LTTB by 10‑50× – see [MEX Acceleration](). ## Lazy Multi-Resolution Pyramid -Problem: At full zoom-out with 50M+ points, scanning all data is O(N). +Problem: at full zoom‑out, scanning 50M+ points is O(N) and unacceptable. -Solution: Pre-computed MinMax pyramid with configurable reduction factor (default 100x per level): +Solution: a pre‑computed MinMax pyramid with a configurable reduction factor (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 1: 100× reduction ( 500,000 points) +Level 2: 100× reduction ( 5,000 points) ``` -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. +When the user zooms out, the coarsest level with enough resolution is selected. A full zoom‑out reads level 2 (5 K points) and downsamples to ~4 K in under **1 ms**. + +Each level is **built lazily** on first access. The initial build cost (≈70 ms with MEX) is paid once; subsequent queries are instant. -Levels are built lazily on first access — the first zoom-out pays a one-time build cost (~70ms with MEX), subsequent queries are instant. +The pyramid is maintained in memory for active lines and, for disk‑backed data (`FastSenseDataStore`), a pre‑computed L1 MinMax pyramid is stored alongside chunks to accelerate zoom‑out without touching raw data. ## MEX Acceleration -Optional C MEX functions with SIMD intrinsics (AVX2 on x86_64, NEON on arm64): +Optional C MEX functions with SIMD intrinsics (AVX2 on x86‑64, NEON on ARM64) accelerate the most performance‑critical paths: | 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 | +| `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()` (legacy) | +| `resolve_disk_mex` | significant | SQLite disk‑based resolution | +| `build_store_mex` | 2‑3× | Bulk SQLite writer for `DataStore` init | +| `to_step_function_mex` | significant | SIMD step‑function conversion for thresholds | + +Plus the `mksqlite` interface (compiled with a bundled SQLite3 amalgamation) for high‑speed database access. + +All functions share a common `simd_utils.h` abstraction layer. When MEX is unavailable or compilation fails (e.g., AVX2 failure retries with SSE2), a pure‑MATLAB fallback is used with identical behaviour. The install process determines whether to recompile via a **MEX stamp** (`mex_stamp.m`) that fingerprints all source files. -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 more details. ## Data Flow Architecture -### Core Data Path +### Core Data Path (FastSense) + ``` -Raw Data (X, Y arrays) - ↓ -FastSenseDataStore (optional, for large datasets) - ↓ -Downsampling Engine (MinMax/LTTB) - ↓ -Pyramid Cache (lazy multi-resolution) - ↓ -Graphics Objects (line handles) +Raw Data (X, Y arrays) → FastSenseDataStore (optional, large data) + ↓ + Downsampling Engine (MinMax / LTTB) + ↓ + Pyramid Cache (lazy multi‑resolution) + ↓ + Graphics Objects (line handles) + ↓ + Interactive Display (zooms trigger re‑downsample) +``` + +### Tag‑to‑Widget Data Path (DashboardEngine) + +``` +SensorTag / MonitorTag / DerivedTag … + ↓ (via getXY()) +FastSenseWidget.update() or FastSense.updateData() ↓ -Interactive Display +FastSense internal pipeline (downsample → pyramid → plot) ``` +MonitorTags and DerivedTags are **lazy** – their output is cached and recomputed only when a parent fires an `invalidate()` notification. This observer pattern flows through the entire derivation graph. + ### 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) -## Sensor Threshold Resolution +- **Memory mode**: X/Y arrays held directly in MATLAB workspace. +- **Disk mode**: Data stored as chunked BLOBs in a SQLite database via `FastSenseDataStore`. +- **Auto mode**: Automatically switches to disk when total data size exceeds `MemoryLimit` (default 500 MB). -The `Sensor.resolve()` algorithm is segment-based: +## Sensor Threshold Resolution (v2.0) -1. Collect all state-change timestamps from all StateChannels -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 +The legacy `Sensor.resolve()` has been superseded by the **Tag** domain model. Instead of evaluating definitions per segment, threshold violations are generated by **MonitorTag** objects: -Complexity: O(S × R) where S = state segments and R = rules, instead of O(N × R) per-point evaluation. +1. A `MonitorTag` wraps a parent `Tag` (e.g., `SensorTag` or `StateTag`) and a `ConditionFn` (e.g., `@(x,y) y > 50`). +2. `getXY()` returns a 0/1 time series aligned to the parent’s native grid (lazy‑evaluated). +3. **Hysteresis** is supported via an optional `AlarmOffConditionFn`. +4. **Debouncing** with `MinDuration` ensures that transient violations shorter than a minimum time are not reported. +5. **CompositeTag** aggregates multiple `MonitorTag`/`CompositeTag` children using boolean operators (AND, OR, MAJORITY, COUNT, WORST, SEVERITY) or a custom function. +6. **DerivedTag** produces continuous (X,Y) signals computed from N parent Tags. + +Live detection uses `LiveEventPipeline`, which polls `DataSource` objects, pushes new data into parent Tags, and then calls `appendData` on `MonitorTag` objects. Events are emitted, stored via `EventStore`, and may trigger `NotificationService` for email alerts. `EventTimelineWidget` and `FastSense` (via `EventStore.Events`) display detected events. ## Disk-Backed Data Storage -For datasets exceeding available memory (100M+ points), `FastSenseDataStore` provides SQLite-backed chunked storage: +`FastSenseDataStore` provides SQLite‑backed chunked storage for datasets exceeding available memory: -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 +1. Data is split into chunks (~10 K‑500 K points each, auto‑tuned). +2. Each chunk is stored as a pair of typed BLOBs (`X` and `Y`) with the chunk’s X‑range indexed for fast overlap queries. +3. On zoom/pan, only chunks intersecting the visible range are loaded. +4. A pre‑computed L1 MinMax pyramid (stored alongside chunks) enables instant zoom‑out without touching raw data. +5. Monitoring tags can persist their derived (X,Y) using `storeMonitor` / `loadMonitor`, validated via a quad‑signature for cache freshness. -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 thousands of individual SQLite round‑trips. -If SQLite is unavailable, a binary file fallback is used automatically. +When SQLite is unavailable, a binary‑file fallback is used automatically. ## Theme Inheritance +Themes cascade with a clear hierarchy: + ``` -Element override > Tile theme > Figure theme > 'default' preset +Element override → Tile theme → Figure theme → 'default' preset ``` -Each level fills in only the fields it specifies; unspecified fields cascade from the next level. +`FastSenseTheme` defines standard plot properties (colors, fonts, line styles). `DashboardTheme` extends these with dashboard‑specific fields (widget backgrounds, header colors, gauge colours). Any property not explicitly set at a given level inherits from the next broader level. ## Dashboard Architecture ### FastSenseGrid vs DashboardEngine -- **[[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 +- **[[FastPlot|FastSenseGrid]]**: A simple tiled grid of `FastSense` instances with synchronized live mode – ideal for quick, multi‑panel time‑series dashboards. +- **[[Dashboard Engine Guide|DashboardEngine]]**: A full widget‑based system supporting gauges, numbers, status indicators, tables, event timelines, and an interactive edit mode. ### DashboardEngine Components ``` DashboardEngine -├── DashboardToolbar — Top toolbar (Live, Edit, Save, Export, Sync) -├── 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 +├── DashboardToolbar — Top toolbar (Live, Export, Image, Config, Events) +├── 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 +├── TimeRangeSelector — Dual‑slider time scrubber with preview envelope +├── DashboardPage — Named page container for multi‑page dashboards └── Widgets (DashboardWidget subclasses) - ├── FastSenseWidget — FastSense instance (Sensor/DataStore/inline) - ├── GaugeWidget — Arc/donut/bar/thermometer gauge - ├── NumberWidget — Big number with trend arrow - ├── StatusWidget — Colored dot indicator - ├── TextWidget — Static label or header - ├── TableWidget — uitable display - ├── RawAxesWidget — User-supplied plot function - ├── EventTimelineWidget — Colored event bars on timeline - ├── GroupWidget — Collapsible panels, tabbed containers - └── MultiStatusWidget — Grid of sensor status dots + ├── FastSenseWidget — FastSense instance (Tag / DataStore / inline) + ├── GaugeWidget — Arc/donut/bar/thermometer gauge + ├── NumberWidget — Big number with trend arrow + ├── StatusWidget — Colored dot indicator + ├── TextWidget — Static label or header + ├── TableWidget — uitable display + ├── RawAxesWidget — User‑supplied plot function + ├── EventTimelineWidget — Colored event bars on timeline + ├── GroupWidget — Collapsible panels, tabbed containers + ├── MultiStatusWidget — Grid of sensor status dots + ├── IconCardWidget — Compact card with icon, value, label + ├── ChipBarWidget — Horizontal row of mini status chips + ├── SparklineCardWidget — KPI card with sparkline + delta + ├── DividerWidget — Horizontal separator + ├── BarChartWidget — Bar chart + ├── ScatterWidget — Scatter plot + ├── HeatmapWidget — 2‑D colour map + ├── HistogramWidget — Histogram + │── ImageWidget — Embedded image + └── DetachedMirror — Live clone in standalone figure ``` -### 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 - -### 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 - -### Edit Mode +### Render and Live 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 - -### 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. -- **Export script:** generates a `.m` file with `DashboardEngine` constructor calls and `addWidget` calls for each widget. +- **Render flow**: Figure creation → theme application → toolbar/time‑slider → `allocatePanels` (lazy widget rendering) → `updateGlobalTimeRange()`. +- **Live mode**: A single timer calls `updateLiveTimeRange()` and then `refresh()` on every widget. Widgets that support incremental updates (like `FastSenseWidget`) use `updateData()` to avoid full rebuilds. A stale‑data banner appears when no new data is detected. +- **Edit mode**: Clicking the Edit button activates `DashboardBuilder`, which overlays drag/resize rectangles, a widget palette sidebar, and a properties panel. +- **Export**: `DashboardSerializer` can export the layout to JSON or a standalone `.m` script. ## Event Detection Architecture -The event detection system provides real-time threshold violation monitoring with configurable notifications and data persistence. - -### Core Components +The event system uses the **MonitorTag** pipeline instead of legacy `Sensor.resolve()`. 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 -└── EventViewer — Interactive Gantt chart + filterable table +├── MonitorTargets — containers.Map of key → MonitorTag +├── EventStore — Atomic .mat file persistence +├── NotificationService — Rule‑based email alerts with snapshots +└── EventViewer — 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 +### Detection Flow -### Event Detection Flow +1. `LiveEventPipeline.runCycle()` polls all data sources. +2. New data is split per sensor and fed into the corresponding `MonitorTarget` via `appendData()`. +3. Inside `MonitorTag`, the hysteresis FSM and `MinDuration` debouncing produce binary alarm states. +4. Events are grouped, assigned an `Id`, and stored via `EventStore.append()`. +5. `EventBinding` links events to tag keys for many‑to‑many queries. +6. `NotificationService` evaluates rules and sends email alerts (optionally with plot snapshots). +7. Active `EventViewer` instances auto‑refresh to display new events. -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 +Severity escalation (`Severity` field) and manual annotations are supported via `Event` handles and the `EventDetails` popup. -### Escalation Logic +## Progress Indication -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 +`ConsoleProgressBar` provides hierarchical, non‑blocking progress output to the command window: -## Progress Indication +- Single‑line updates with backspace‑based overwriting. +- Indentation support for nested operations (e.g., dock → tabs → tiles). +- `freeze()` / `finish()` transition a bar to a permanent line, allowing the next bar to start below. -`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 +`DashboardProgress` wraps this for the dashboard render pass, showing per‑widget progress while respecting the configured `ProgressMode` (auto/on/off). ## 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 toggles, autoscale, export, live toggle. +- **DashboardToolbar**: Live, Export Image, Config, Events toggle, Info modal. +- **NavigatorOverlay**: Minimap with draggable zoom rectangle (used in `SensorDetailPlot`). +- **HoverCrosshair**: A vertical crosshair + multi‑line datatip that follows the mouse cursor. Coexists with the toolbar’s crosshair mode without conflict. ### 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 a `LinkGroup` string. When one plot’s `XLim` changes, the new limits are broadcast to all members of the same group. + +### Detached Mirrors +Any [`DashboardWidget`]() can be popped out into a standalone figure window using `DashboardEngine.detachWidget()`. The detached mirror is a lightweight clone that stays live via the main dashboard’s timer, refreshing its content without maintaining a full duplicate pipeline. + +### Info Panel +DashboardEngine supports a linked Markdown file (`InfoFile`). It is rendered to HTML (`MarkdownRenderer`) and displayed either in‑app (modal `uifigure` with `uihtml`) or in a system browser, providing contextual documentation for the dashboard. diff --git a/wiki/Dashboard-Engine-Guide.md b/wiki/Dashboard-Engine-Guide.md index 2fff83a1..677e5c8d 100644 --- a/wiki/Dashboard-Engine-Guide.md +++ b/wiki/Dashboard-Engine-Guide.md @@ -12,18 +12,22 @@ 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 | +| Grid | Fixed rows × cols | 24‑column responsive | +| Tile content | FastSense instances only | 15+ widget types (plots, gauges, cards, charts, tables, group containers) | +| Multi‑page support | No | Yes — `addPage()`, `switchPage()` | +| Persistence | None | JSON save/load + `.m` script export | +| Visual editor | Yes (limited) | Full 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 | +| Sensor binding | Via `addSensor` per tile | Direct widget property (auto‑title, auto‑units, auto‑thresholds) | +| Live mode | Per‑figure timer | Engine‑level timer refreshing all widgets | +| Group containers & tabs | None | `GroupWidget` (panel, collapsible, tabbed) | +| Config dialog | No | `DashboardConfigDialog` for runtime property editing | +| Progress feedback | None | `DashboardProgress` with terminal progress bar | **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, multi‑page layouts, group containers, JSON persistence, or the full visual editor. --- @@ -48,6 +52,11 @@ d.addWidget('number', 'Title', 'Latest Value', ... 'Position', [1 7 8 2], ... 'StaticValue', y(end), 'Units', 'V'); +% Sparkline KPI card +d.addWidget('sparklinecard', 'Title', 'Rolling Stats', ... + 'Position', [9 7 8 2], ... + 'StaticValue', mean(y), 'SparkData', y, 'Units', 'V'); + d.render(); ``` @@ -55,36 +64,66 @@ d.render(); ## Grid System -DashboardEngine uses a **24-column grid**. Widget positions are specified as: +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 +- `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: ```matlab -[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 +[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. --- +## Multi‑Page Dashboards + +DashboardEngine can organise widgets into named pages, allowing you to build tab‑like navigation without separate figures. + +```matlab +d = DashboardEngine('Plant Monitor'); +d.Theme = 'light'; + +% Create pages +pg1 = d.addPage('Overview'); +d.addWidget('fastsense', 'Title', 'Temp', 'Position', [1 1 12 6], 'Tag', sTemp); +d.addWidget('gauge', 'Title', 'Flow', 'Position', [13 1 12 6], 'Tag', sFlow); + +pg2 = d.addPage('Details'); +d.addWidget('heatmap', 'Title', 'Sensor Grid', 'Position', [1 1 24 8], ... + 'DataFcn', @() sensorMatrix); + +% Switch to page 2 programmatically +d.switchPage(2); +% Back to page 1 +d.switchPage(1); +``` + +When `Pages` is non‑empty, `addWidget()` always routes to the active page. The toolbar automatically shows a page‑bar with navigation controls. + +--- + ## Widget Types -### FastSense (time series) +DashboardEngine supports a rich catalog of widgets, each with its own data‑binding modes. This section covers them by category. +### Time‑Series & Plot Widgets + +#### FastSense (time series) ```matlab -% Sensor-bound (recommended) -d.addWidget('fastsense', 'Position', [1 1 12 8], 'Sensor', mySensor); +% Sensor‑bound (recommended) +d.addWidget('fastsense', 'Sensor', mySensor, 'Position', [1 1 12 8]); % Inline data d.addWidget('fastsense', 'Title', 'Raw', 'Position', [13 1 12 8], ... @@ -98,127 +137,210 @@ d.addWidget('fastsense', 'Title', 'File', 'Position', [1 9 24 6], ... d.addWidget('fastsense', 'Title', 'Store', 'Position', [1 15 24 6], ... 'DataStore', myDataStore); ``` +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. -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. +#### Raw Axes (custom plots) +```matlab +d.addWidget('rawaxes', 'Title', 'Distribution', 'Position', [1 5 8 4], ... + 'PlotFcn', @(ax) histogram(ax, tempData, 50, ... + 'FaceColor', [0.31 0.80 0.64], 'EdgeColor', 'none')); -### Number (big value display) +% Sensor‑bound with time range +d.addWidget('rawaxes', 'Title', 'Custom Analysis', 'Position', [9 5 8 4], ... + 'Sensor', mySensor, ... + 'PlotFcn', @(ax, sensor, tRange) plotCustom(ax, sensor, tRange)); +``` +The `PlotFcn` receives MATLAB axes as the first argument. When Sensor‑bound, it also receives the Sensor object and optionally a time range. +#### Scatter (two‑sensor correlation) +```matlab +d.addWidget('scatter', 'Title', 'Temp vs Flow', 'Position', [1 9 6 4], ... + 'SensorX', sTemp, 'SensorY', sFlow); +% Optional color‑by third sensor +d.addWidget('scatter', 'Title', 'Color Mapped', 'Position', [7 9 6 4], ... + 'SensorX', sTemp, 'SensorY', sFlow, 'SensorColor', sPress); +``` +Properties: `SensorX`, `SensorY`, `SensorColor`, `MarkerSize`, `Colormap`. + +### KPI & Value Widgets + +#### Number (big value display) ```matlab d.addWidget('number', 'Title', 'Temperature', ... 'Position', [1 1 6 2], ... 'Sensor', sTemp, 'Units', 'degF', 'Format', '%.1f'); -% Or with static value +% Static value d.addWidget('number', 'Title', 'Total Count', ... 'Position', [7 1 6 2], ... 'StaticValue', 1234, 'Units', 'pcs', 'Format', '%d'); -% Or with function callback +% Function callback d.addWidget('number', 'Title', 'CPU Load', ... 'Position', [13 1 6 2], ... 'ValueFcn', @() getCpuLoad(), 'Units', '%', 'Format', '%.0f'); ``` +Shows a large number with a trend arrow (up/down/flat) computed from recent sensor data. + +#### SparklineCard (KPI card with mini‑chart) +```matlab +% Sensor‑driven: uses Sensor.Y for both value and sparkline +d.addWidget('sparklinecard', 'Title', 'CPU Load', ... + 'Position', [1 1 6 2], 'Sensor', sCpu, 'Units', '%'); + +% Static value with separate sparkline history +d.addWidget('sparklinecard', 'Title', 'Rolling Average', ... + 'Position', [7 1 6 2], 'StaticValue', 42.0, ... + 'SparkData', cpuHistory, 'Units', '%'); +``` +Properties: `StaticValue`, `ValueFcn`, `Units`, `Format`, `NSparkPoints`, `ShowDelta`, `SparkColor`, `SparkData`. -Shows a large number with a trend arrow (up/down/flat) computed from recent sensor data. Layout: `[Title | Value+Trend | Units]`. +#### IconCard (mushroom‑style KPI) +```matlab +d.addWidget('iconcard', 'Title', 'Pump 1', ... + 'Position', [1 3 4 2], 'StaticValue', 23.5, 'Units', 'L/min'); -### Status (health indicator) +% Sensor‑bound (auto state colour) +d.addWidget('iconcard', 'Title', 'Temp', ... + 'Position', [5 3 4 2], 'Sensor', sTemp, 'StaticState', 'ok'); +``` +Properties: `IconColor` ('auto' or RGB), `StaticValue`, `ValueFcn`, `StaticState` ('ok','warn','alarm','info','inactive'), `Units`, `Format`, `SecondaryLabel`, `Threshold`. +### Status & Health Widgets + +#### Status (single indicator dot) ```matlab -d.addWidget('status', 'Title', 'Pump', ... - 'Position', [7 1 5 2], ... - 'Sensor', sTemp); +% Sensor‑bound (auto threshold state) +d.addWidget('status', 'Title', 'Pump', 'Position', [7 1 5 2], 'Sensor', sTemp); -% Legacy static status -d.addWidget('status', 'Title', 'System', ... - 'Position', [12 1 5 2], ... - 'StaticStatus', 'ok'); % 'ok', 'warning', 'alarm' +% Threshold‑bound (no Sensor required) +w = StatusWidget('Title', 'Temp', 'Threshold', thrObj, 'Value', 85); +w = StatusWidget('Title', 'Temp', 'Threshold', 'temp_hi', 'ValueFcn', @getTemp); ``` +Properties: `StatusFcn`, `StaticStatus`, `Threshold`, `Value`, `ValueFcn`. When Sensor is set, state is derived from threshold rules. -Shows a colored dot (green/amber/red) and the sensor's latest value. Status is derived automatically from threshold rules. +#### MultiStatus (grid of indicators) +```matlab +d.addWidget('multistatus', 'Title', 'Line A', ... + 'Position', [1 5 12 3], 'Sensors', {s1, s2, s3}); +``` +Properties: `Sensors`, `Columns`, `ShowLabels`, `IconStyle` ('dot','square','icon'). -### Gauge (arc/donut/bar/thermometer) +#### ChipBar (horizontal row of mini chips) +```matlab +w = ChipBarWidget('Title', 'System Health'); +w.Chips = { + struct('label', 'Pump', 'statusFcn', @() 'ok'), + struct('label', 'Tank', 'statusFcn', @() 'warn'), + struct('label', 'Fan', 'sensor', fanSensor) +}; +d.addWidget(w); +``` +Each chip struct can contain `label`, `sensor`, `statusFcn`, or `iconColor`. State colours follow the theme’s OK/Warn/Alarm palette. + +### Gauge Widget ```matlab d.addWidget('gauge', 'Title', 'Flow Rate', ... - 'Position', [1 3 8 6], ... - 'Sensor', sFlow, 'Range', [0 160], 'Units', 'L/min', ... - 'Style', 'donut'); + 'Position', [1 3 8 6], 'Sensor', sFlow, ... + 'Range', [0 160], 'Units', 'L/min', 'Style', 'donut'); % Static value d.addWidget('gauge', 'Title', 'Efficiency', ... 'Position', [9 3 8 6], ... - 'StaticValue', 85, 'Range', [0 100], 'Units', '%', ... - 'Style', 'arc'); + 'StaticValue', 85, 'Range', [0 100], 'Units', '%', 'Style', 'arc'); +``` +Styles: `'arc'` (default), `'donut'`, `'bar'`, `'thermometer'`. When Sensor‑bound, range and units are auto‑derived from threshold rules and sensor properties. + +### Chart & Analysis Widgets + +#### BarChart +```matlab +d.addWidget('barchart', 'Title', 'Category Breakdown', 'Position', [1 5 6 4], ... + 'DataFcn', @() struct('categories', {{'A','B','C'}}, 'values', [30 45 25]), ... + 'Orientation', 'vertical', 'Stacked', false); ``` +Properties: `DataFcn` (must return a struct with `categories` and `values`), `Orientation`, `Stacked`. -Styles: `'arc'` (default), `'donut'`, `'bar'`, `'thermometer'`. +#### Histogram +```matlab +d.addWidget('histogram', 'Title', 'Temp Distribution', 'Position', [7 5 6 4], ... + 'DataFcn', @() tempArray, 'NumBins', 30, 'ShowNormalFit', true); +``` +Properties: `DataFcn`, `NumBins`, `ShowNormalFit`, `EdgeColor`. -When Sensor-bound, range and units are auto-derived from threshold rules and sensor properties. +#### Heatmap +```matlab +d.addWidget('heatmap', 'Title', 'Hourly Matrix', 'Position', [13 5 6 4], ... + 'DataFcn', @() rand(10,24), 'Colormap', 'turbo', 'ShowColorbar', true); +``` +Properties: `DataFcn`, `Colormap`, `ShowColorbar`, `XLabels`, `YLabels`. -### Text (labels and headers) +#### Image +```matlab +d.addWidget('image', 'Title', 'Floor Plan', 'Position', [1 7 6 4], ... + 'File', 'floorplan.png', 'Scaling', 'fit'); +``` +Properties: `File`, `ImageFcn`, `Scaling` ('fit','fill','stretch'), `Caption`. +### Text & Layout Widgets + +#### Text (labels and headers) ```matlab d.addWidget('text', 'Title', 'Plant Overview', ... 'Position', [1 1 6 1], ... - 'Content', 'Line 4 - Shift A', 'FontSize', 16, ... - 'Alignment', 'center'); + 'Content', 'Line 4 - Shift A', 'FontSize', 16, 'Alignment', 'center'); ``` -### Table (data display) +#### Divider (horizontal separator) +```matlab +d.addWidget('divider', 'Position', [1 3 24 1]); +``` +Properties: `Thickness` (1=thin,2=medium,3=thick), `Color` (RGB override; empty = theme). +#### GroupWidget (containers) ```matlab -% 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'}}); +% Panel group +g = GroupWidget('Label', 'Sensor Suite', 'Mode', 'panel'); +g.addChild(fastSenseWidget); +g.addChild(numberWidget); +d.addWidget(g); + +% Collapsible group +g = GroupWidget('Label', 'Details', 'Mode', 'collapsible', 'Collapsed', false); +g.addChild(tableWidget); +d.addWidget(g); % or use d.addCollapsible() convenience + +% Tabbed group +g = GroupWidget('Mode', 'tabbed'); +g.addChild(plotWidget, 'Temperature'); +g.addChild(tableWidget, 'Logs'); +d.addWidget(g); +``` +Modes: `'panel'`, `'collapsible'`, `'tabbed'`. Children are layout using an internal sub‑grid; auto‑flow can be disabled with `ChildAutoFlow = false`. Collapsible state toggles show/hide of the child panel. -% Sensor data (last N rows) -d.addWidget('table', 'Title', 'Recent Data', ... - 'Position', [1 9 12 4], ... - 'Sensor', sTemp, 'N', 15); +### Data & Event Widgets -% Dynamic data via callback -d.addWidget('table', 'Title', 'Live Log', ... - 'Position', [1 13 12 4], ... - 'DataFcn', @() getRecentAlarms(), ... - 'ColumnNames', {'Time', 'Tag', 'Value', 'Level'}); +#### Table (data grid) +```matlab +% Sensor‑bound (last N rows) +d.addWidget('table', 'Title', 'Recent Data', ... + 'Position', [1 9 12 4], 'Sensor', sTemp, 'N', 15); % Event mode (requires EventStore) d.addWidget('table', 'Title', 'Events', ... - 'Position', [1 17 12 4], ... - 'Sensor', mySensor, 'Mode', 'events', ... + 'Position', [1 17 12 4], 'Sensor', mySensor, 'Mode', 'events', ... 'EventStoreObj', myEventStore, 'N', 10); -``` - -### Raw Axes (custom plots) -```matlab -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')); - -% Sensor-bound with time range -d.addWidget('rawaxes', 'Title', 'Custom Analysis', ... - 'Position', [9 5 8 4], ... - 'Sensor', mySensor, ... - 'PlotFcn', @(ax, sensor, tRange) plotCustom(ax, sensor, tRange)); +% Callback‑driven +d.addWidget('table', 'Title', 'Live Log', ... + 'Position', [1 13 12 4], 'DataFcn', @() getRecentAlarms(), ... + 'ColumnNames', {'Time','Tag','Value','Level'}); ``` +Properties: `DataFcn`, `Data` (static cell array), `ColumnNames`, `Mode` ('data'/'events'), `N`, `EventStoreObj`. -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 - +#### Event Timeline ```matlab -% 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 (recommended) d.addWidget('timeline', 'Title', 'Alarms', ... 'Position', [1 16 24 3], ... @@ -230,6 +352,7 @@ d.addWidget('timeline', 'Title', 'Temp Events', ... 'EventStoreObj', myEventStore, ... 'FilterSensors', {'T-401', 'T-402'}); ``` +Properties: `EventStoreObj`, `Events` (struct array), `EventFcn`, `FilterSensors`, `FilterTagKey`, `ColorSource`. --- @@ -254,20 +377,55 @@ sTemp.addThresholdRule(struct('machine', 1), 85, ... 'Direction', 'upper', 'Label', 'Hi Alarm'); sTemp.resolve(); -% All of these auto-derive from the Sensor: +% 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]); +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` +- **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 +- **Thresholds:** FastSenseWidget renders resolved thresholds and violations; StatusWidget checks the latest value against all rules; GaugeWidget auto‑sets range. +- **Live refresh:** calling `refresh()` re‑reads the sensor data + +--- + +## Grouping and Multi‑Page Organization + +### GroupWidget + +GroupWidget acts as a container for other widgets. Use it to create logical sections, collapsible panels, or tabbed views. + +```matlab +% Collapsible panel +g = GroupWidget('Label', 'Temperature Details', 'Mode', 'collapsible'); +g.addChild(fastSenseWidget); +g.addChild(histWidget); +d.addWidget(g); + +% Equivalent shortcut +d.addCollapsible('Temperature Details', {fastSenseWidget, histWidget}, ... + 'Collapsed', true); +``` + +When a `GroupWidget` is collapsed, its children are hidden and the layout is refined. The container can be expanded/collapsed at runtime. + +### Multi‑Page Dashboards + +Beyond grouping, you can split widgets across named pages using `addPage()` / `switchPage()`. This is ideal for dashboards with too many widgets to fit on one scrollable canvas. The toolbar shows a page bar for navigation; `ActivePage` tracks the current page. + +```matlab +d.addPage('Overview'); +d.addWidget(...); % to Overview +d.addPage('Alarms'); +d.addWidget(...); % to Alarms +d.switchPage(1); +``` + +Page‑state is serialized in JSON and the export script. --- @@ -279,7 +437,7 @@ Benefits of Sensor binding: 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. +The JSON file contains the dashboard name, theme, live interval, grid settings, multi‑page info, and each widget’s type, title, position, and data source. ### Load from JSON @@ -288,7 +446,7 @@ d2 = DashboardEngine.load('dashboard.json'); d2.render(); ``` -To re-bind Sensor objects on load, provide a resolver function: +To re‑bind Sensor objects on load, provide a resolver function: ```matlab d2 = DashboardEngine.load('dashboard.json', ... @@ -302,21 +460,21 @@ d2.render(); d.exportScript('rebuild_dashboard.m'); ``` -Generates a readable `.m` file with `DashboardEngine` constructor and `addWidget` calls that recreates the dashboard. +Generates a readable `.m` file with `DashboardEngine` constructor, calls to `addPage()`, `switchPage()`, and `addWidget()` calls that recreate the dashboard (including groups). The output is a **function** that returns the engine, so you can execute it directly with `feval`. --- ## Theming -DashboardEngine uses `DashboardTheme`, which extends `FastSenseTheme` with dashboard-specific fields (widget backgrounds, border colors, status indicator colors, etc.). +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', 'industrial', 'scientific', 'ocean' +d.Theme = 'dark'; % or 'light' d.render(); ``` -Available presets: `'default'`, `'dark'`, `'light'`, `'industrial'`, `'scientific'`, `'ocean'`. +Available presets: `'light'` and `'dark'`. Legacy presets (`'default'`, `'industrial'`, `'scientific'`, `'ocean'`) are aliased to `'light'` for backwards compatibility. You can also override specific theme properties: @@ -325,6 +483,8 @@ theme = DashboardTheme('dark', 'WidgetBackground', [0.1 0.1 0.2]); d.Theme = theme; ``` +Theme fields include: `DashboardBackground`, `WidgetBackground`, `WidgetBorderColor`, `ToolbarBackground`, `ToolbarFontColor`, `DragHandleColor`, `DropZoneColor`, `GridLineColor`, `StatusOkColor`, `StatusWarnColor`, `StatusAlarmColor`, `GaugeArcWidth`, `KpiFontSize`, and many more. + --- ## Live Mode @@ -337,7 +497,7 @@ 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.addWidget('number', 'Sensor', sTemp, 'Position', [1 9 12 2]); d.render(); d.startLive(); % start periodic refresh @@ -345,7 +505,7 @@ 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. If a widget’s data has not advanced on a live tick, a **stale‑data banner** appears with a warning. --- @@ -353,11 +513,12 @@ You can also toggle live mode from the toolbar's Live button. The toolbar shows 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 +- **FastSenseWidget:** sets `xlim` on the FastSense axes +- **EventTimelineWidget:** sets `xlim` on the timeline axes +- **RawAxesWidget:** passes the time range to the `PlotFcn` +- **GroupWidget:** recursively forwards to children -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. --- @@ -365,40 +526,63 @@ 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 -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 +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. +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 +- `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 --- -## Info File Integration +## Info File & Dashboard Config + +### Info File Integration Dashboards can link to external Markdown documentation files: ```matlab -d = DashboardEngine('My Dashboard'); 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 a system browser (or an in‑app modal if supported). Basic Markdown syntax is supported (headings, lists, code blocks, tables). + +### DashboardConfigDialog + +A built‑in dialog allows runtime editing of the dashboard’s public properties (Name, Theme, LiveInterval, ProgressMode, ShowTimePanel, etc.). Access it via the toolbar’s **Config** button: + +```matlab +% Programmatically +dlg = DashboardConfigDialog(engine); % non‑modal, apply/close +``` + +Enum‑like properties (`Theme`, `ProgressMode`) are shown as popup menus; numeric fields as numeric edits; others as text. + +### Progress Bar + +During render (especially with many widgets), DashboardEngine emits a self‑updating progress line to the console. Control its visibility with: + +```matlab +d.ProgressMode = 'auto'; % default: only in interactive sessions +d.ProgressMode = 'on'; % always show +d.ProgressMode = 'off'; % never show +``` + +`DashboardProgress` is the internal class responsible for this feedback. --- ## Complete Example -This example creates a process monitoring dashboard with sensor-bound widgets: +This example creates a multi‑page process monitoring dashboard with sensor‑bound widgets and grouping: ```matlab install; @@ -419,10 +603,8 @@ sTemp.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('machine',1), 78, 'Direction','upper','Label','Hi Warn'); +sTemp.addThresholdRule(struct('machine',1), 85, 'Direction','upper','Label','Hi Alarm'); sTemp.resolve(); % Pressure sensor @@ -430,8 +612,8 @@ sPress = Sensor('P-201', 'Name', 'Pressure'); sPress.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'); -sPress.addThresholdRule(struct(), 70, 'Direction', 'upper', 'Label', 'Hi Alarm'); +sPress.addThresholdRule(struct(), 65, 'Direction','upper','Label','Hi Warn'); +sPress.addThresholdRule(struct(), 70, 'Direction','upper','Label','Hi Alarm'); sPress.resolve(); %% Build dashboard @@ -439,29 +621,39 @@ d = DashboardEngine('Process Monitoring — Line 4'); d.Theme = 'light'; d.LiveInterval = 5; +% Page 1: Overview +d.addPage('Overview'); + % 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], ... +d.addWidget('number', 'Title', 'Temp', 'Position', [5 1 5 2], ... 'Sensor', sTemp, 'Format', '%.1f'); -d.addWidget('number', 'Title', 'Pressure', 'Position', [10 1 5 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], ... +d.addWidget('sparklinecard', 'Title', 'Pressure Trend', 'Position', [10 1 5 2], ... 'Sensor', sPress); +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 +% 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], ... +d.addWidget('gauge', 'Title', 'Pressure Gauge', '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')); +% Page 2: Logs (grouped) +d.addPage('Logs'); +g = GroupWidget('Label', 'Event Logs', 'Mode', 'tabbed'); +g.addChild(d.addWidget('table', 'Title', 'Alarms', ... + 'Position', [1 1 24 8], 'DataFcn', @() getAlarms()), 'Alarms'); +g.addChild(d.addWidget('timeline', 'Title', 'Event Timeline', ... + 'Position', [1 9 24 6], 'EventStoreObj', eventStore), 'Timeline'); +d.addWidget(g); + d.render(); %% Save @@ -472,7 +664,7 @@ d.save(fullfile(tempdir, 'process_dashboard.json')); ## 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 +- [[Examples]] — `example_dashboard_engine`, `example_dashboard_all_widgets` diff --git a/wiki/Home.md b/wiki/Home.md index 13a0f418..2e83794b 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -8,10 +8,10 @@ Ultra-fast time series plotting for MATLAB and GNU Octave with dynamic downsampl | Metric | Value | |--------|-------| -| 10M point zoom cycle | 4.7 ms (212 FPS) | -| Point reduction | 99.96% (10M to ~4K displayed) | -| GPU memory (10M pts) | 0.06 MB vs 153 MB for plot() | -| Implementation | Pure MATLAB + optional C MEX (AVX2/NEON SIMD) | +| 10M point zoom cycle | 4.7 ms (212 FPS) | +| Point reduction | 99.96% (10M to ≈4K displayed) | +| GPU memory (10M pts) | 0.06 MB vs 153 MB for `plot()` | +| Implementation | Pure MATLAB + optional C MEX (AVX2/NEON SIMD) | ## Library Components @@ -19,25 +19,25 @@ FastPlot consists of five integrated libraries: | Library | Description | |---------|-------------| -| **FastSense** | Core plotting engine with dynamic downsampling, dashboard layouts (FastSenseGrid, FastSenseDock), interactive toolbar, themes, and disk-backed storage via FastSenseDataStore | +| **FastSense** | Core plotting engine with dynamic downsampling, dashboard layouts (`FastSenseGrid`, `FastSenseDock`), interactive toolbar, themes, and disk-backed storage via `FastSenseDataStore` | | **Dashboard** | Widget-based dashboard engine with 8 widget types, 24-column responsive grid, edit mode, and JSON persistence | -| **SensorThreshold** | Sensor data containers with state-dependent threshold rules, violation detection, and SensorRegistry catalog | -| **EventDetection** | Event detection from threshold violations, EventViewer with Gantt timeline, live pipeline with notifications | -| **WebBridge** | TCP server for web-based visualization with NDJSON protocol | +| **SensorThreshold** | Tag‑based sensor data containers (`SensorTag`, `StateTag`, `MonitorTag`, `CompositeTag`, `DerivedTag`), state‑dependent threshold rules, and `TagRegistry` catalog | +| **EventDetection** | Event detection from threshold violations, `EventViewer` with Gantt timeline, live pipeline with notifications | +| **WebBridge** | TCP server for web‑based visualization with NDJSON protocol | ## Features -- **Smart downsampling** — per-pixel MinMax and LTTB algorithms, auto-selected per zoom level -- **Pyramid cache** — multi-resolution pre-computation for instant zoom-out on 50M+ datasets -- **MEX acceleration** — optional C with SIMD (AVX2/NEON), auto-fallback to pure MATLAB -- **Dashboard layouts** — tiled grids (FastSenseGrid) and tabbed containers (FastSenseDock) +- **Smart downsampling** — per‑pixel MinMax and LTTB algorithms, auto‑selected per zoom level +- **Pyramid cache** — multi‑resolution pre‑computation for instant zoom‑out on 50M+ datasets +- **MEX acceleration** — optional C with SIMD (AVX2/NEON), auto‑fallback to pure MATLAB +- **Dashboard layouts** — tiled grids (`FastSenseGrid`) and tabbed containers (`FastSenseDock`) - **Interactive toolbar** — data cursor, crosshair, grid/legend toggle, autoscale, PNG export -- **6 built-in themes** — default, dark, light, industrial, scientific, ocean -- **Linked axes** — synchronized zoom/pan across subplots -- **Sensor system** — state-dependent thresholds with condition-based rules and violation markers -- **Event detection** — group violations into events with statistics, Gantt viewer, click-to-plot -- **Live mode** — file polling with auto-refresh (preserve/follow/reset view modes) -- **Disk-backed storage** — SQLite-backed chunked DataStore for 100M+ point datasets +- **6 built‑in themes** — light, dark, vibrant, muted, colourblind, ocean +- **Linked axes** — synchronised zoom/pan across subplots +- **Tag‑based sensor system** — unified domain model with listeners, lazy evaluation, and disk persistence +- **Event detection** — group violations into events with statistics, Gantt viewer, click‑to‑plot +- **Live mode** — file polling with auto‑refresh (preserve/follow/reset view modes) +- **Disk‑backed storage** — SQLite‑backed chunked `DataStore` for 100M+ point datasets ## Quick Start @@ -71,19 +71,11 @@ fig.renderAll(); ``` ```matlab -% Sensor with state-dependent thresholds -s = Sensor('pressure', 'Name', 'Chamber Pressure'); -s.X = linspace(0, 100, 1e6); -s.Y = randn(1, 1e6) * 10 + 50; - -sc = StateChannel('machine'); -sc.X = [0 30 60 80]; sc.Y = [0 1 2 1]; -s.addStateChannel(sc); -s.addThresholdRule(struct('machine', 1), 70, 'Direction', 'upper', 'Label', 'Run HI'); -s.resolve(); - +% SensorTag and addTag +st = SensorTag('pressure', 'Name', 'Chamber Pressure', ... + 'X', linspace(0, 100, 1e6), 'Y', randn(1, 1e6)*10 + 50); fp = FastSense('Theme', 'industrial'); -fp.addSensor(s, 'ShowThresholds', true); +fp.addTag(st); fp.render(); ``` @@ -95,20 +87,20 @@ fp.render(); ## Getting Started -Start with the [[Installation]] guide to set up FastPlot and compile MEX acceleration. Then follow the [[Getting Started]] tutorial for step-by-step examples covering basic plotting, dashboards, sensors, and live mode. +Start with the [[Installation]] guide to set up FastPlot and compile MEX acceleration. Then follow the [[Getting Started]] tutorial for step‑by‑step examples covering basic plotting, dashboards, sensors, and live mode. ## API Reference **Core Classes** - [[API Reference: FastPlot]] — main plotting engine with dynamic downsampling -- [[API Reference: Dashboard]] — FastSenseGrid, FastSenseDock, FastSenseToolbar -- [[API Reference: Sensors]] — Sensor, StateChannel, ThresholdRule, SensorRegistry -- [[API Reference: Event Detection]] — EventDetector, EventViewer, LiveEventPipeline -- [[API Reference: Themes]] — theme presets, customization, color palettes -- [[API Reference: Utilities]] — ConsoleProgressBar, FastSenseDefaults +- [[API Reference: Dashboard]] — `FastSenseGrid`, `FastSenseDock`, `FastSenseToolbar` +- [[API Reference: Sensors]] — `SensorTag`, `StateTag`, `MonitorTag`, `CompositeTag`, `DerivedTag`, `TagRegistry` +- [[API Reference: Event Detection]] — `EventDetector`, `EventViewer`, `LiveEventPipeline` +- [[API Reference: Themes]] — theme presets, customisation, colour palettes +- [[API Reference: Utilities]] — `ConsoleProgressBar`, `FastSenseDefaults` **Specialized Guides** - [[Live Mode Guide]] — file polling, view modes, live dashboards -- [[Dashboard Engine Guide]] — DashboardEngine with widget-based dashboards +- [[Dashboard Engine Guide]] — `DashboardEngine` with widget‑based dashboards - [[Datetime Guide]] — working with time series data -- [[Examples]] — 40+ categorized runnable examples +- [[Examples]] — 40+ categorised runnable examples