diff --git a/wiki/Architecture.md b/wiki/Architecture.md index 3f90ff9b..86ea8be8 100644 --- a/wiki/Architecture.md +++ b/wiki/Architecture.md @@ -4,7 +4,7 @@ ## 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 cache and re-downsamples only the visible range on every interaction. ## Project Structure @@ -22,30 +22,33 @@ FastPlot/ │ │ ├── SensorDetailPlot.m # Sensor detail view with state bands │ │ ├── NavigatorOverlay.m # Minimap zoom navigator │ │ ├── ConsoleProgressBar.m # Progress indication +│ │ ├── HoverCrosshair.m # Hover crosshair + multi-line datatip │ │ ├── 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 +│ │ ├── Tag.m # Abstract base for all tags +│ │ ├── SensorTag.m # Inline/time-series sensor tag +│ │ ├── StateTag.m # Discrete state tag +│ │ ├── MonitorTag.m # 0/1 binary monitor tag +│ │ ├── CompositeTag.m # Multi-child aggregation tag +│ │ ├── DerivedTag.m # Continuous derived tag +│ │ ├── TagRegistry.m # Singleton catalog of tags +│ │ ├── LiveTagPipeline.m # Timer-driven raw-data → tag .mat pipeline +│ │ ├── BatchTagPipeline.m # Synchronous raw-data → tag .mat pipeline │ │ └── private/ # Resolution algorithms │ ├── EventDetection/ # Event detection and viewer │ │ ├── Event.m -│ │ ├── EventDetector.m +│ │ ├── EventBinding.m # Many-to-many event↔tag registry +│ │ ├── 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 +│ │ └── * # Utilities (eventLogger, generateEventSnapshot, printEventSummary) │ ├── Dashboard/ # Dashboard engine (serializable) │ │ ├── DashboardEngine.m │ │ ├── DashboardBuilder.m @@ -69,7 +72,13 @@ FastPlot/ │ │ ├── HeatmapWidget.m │ │ ├── HistogramWidget.m │ │ ├── ImageWidget.m -│ │ └── MarkdownRenderer.m # HTML conversion for info panels +│ │ ├── ChipBarWidget.m # Row of mini status chips +│ │ ├── IconCardWidget.m # Compact KPI card +│ │ ├── SparklineCardWidget.m # Big number + sparkline +│ │ ├── DividerWidget.m # Horizontal separator +│ │ ├── TimeRangeSelector.m # Slider-based time window control +│ │ ├── MarkdownRenderer.m # HTML conversion for info panels +│ │ └── severityColor.m # Severity → color mapping │ └── WebBridge/ # TCP server for web visualization │ ├── WebBridge.m │ └── WebBridgeProtocol.m @@ -79,23 +88,23 @@ FastPlot/ ## 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()` on a `FastSense` instance. +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. ## 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 @@ -118,9 +127,9 @@ Both algorithms handle NaN gaps by segmenting contiguous non-NaN regions indepen ## 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 (default 100x per level): ``` Level 0: Raw data (50,000,000 points) @@ -175,12 +184,12 @@ Interactive Display The `Sensor.resolve()` algorithm is segment-based: -1. Collect all state-change timestamps from all StateChannels +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 + - 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. Complexity: O(S × R) where S = state segments and R = rules, instead of O(N × R) per-point evaluation. @@ -209,14 +218,14 @@ Each level fills in only the fields it specifies; unspecified fields cascade fro ### 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 +- **[[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 Components ``` DashboardEngine -├── DashboardToolbar — Top toolbar (Live, Edit, Save, Export, Sync) +├── DashboardToolbar — Top toolbar (Live, Edit, Save, Export, Sync, Config) ├── DashboardLayout — 24-column responsive grid with scrollable canvas ├── DashboardTheme — FastSenseTheme + dashboard-specific fields ├── DashboardBuilder — Edit mode overlay (drag/resize, palette, properties) @@ -231,81 +240,84 @@ DashboardEngine ├── RawAxesWidget — User-supplied plot function ├── EventTimelineWidget — Colored event bars on timeline ├── GroupWidget — Collapsible panels, tabbed containers - └── MultiStatusWidget — Grid of sensor status dots + ├── MultiStatusWidget — Grid of sensor status dots + ├── BarChartWidget — Bar chart + ├── ScatterWidget — Scatter plot + ├── HeatmapWidget — Heatmap + ├── HistogramWidget — Histogram + ├── ImageWidget — Image display + ├── ChipBarWidget — Row of status chips + ├── IconCardWidget — KPI card with icon + ├── SparklineCardWidget — Big number + sparkline + └── DividerWidget — Horizontal separator ``` ### 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. `TimeRangeSelector` (time slider with preview envelope) is created at the bottom. +5. `DashboardLayout.createPanels()` computes grid positions, creates viewport/canvas/scrollbar, and allocates 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 +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. +- **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. +- **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 uses **MonitorTag** to derive binary alarm/ok signals from raw sensor tags. Live monitoring is orchestrated by `LiveEventPipeline`. ### 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: 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.start()` launches a timer that fires `runCycle()` periodically. +2. Each cycle polls all data sources for new X/Y data. +3. For each registered MonitorTag: + - The parent tag’s `updateData` is called with the new data. + - `monitor.appendData(newX, newY)` is called to extend the binary alarm signal. +4. The MonitorTag internally detects rising/falling edges, respects `MinDuration` debounce, and emits `Event` objects via its `EventStore` (if bound). +5. `NotificationService` sends rule-based email alerts with plot snapshots. +6. Any active `EventViewer` instances auto-refresh. + +**Order invariant** (Pitfall Y): parent tag’s `updateData` MUST be called before `monitor.appendData` to ensure the monitor always computes on the latest raw data. The pipeline enforces this. ### 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 +When `EscalateSeverity` is enabled, events are promoted to the highest violated threshold. For example, a “Warning” event that later crosses an “Alarm” threshold is escalated to “Alarm” severity. ## Progress Indication @@ -317,9 +329,11 @@ When `EscalateSeverity` is enabled, events are promoted to the highest violated ## 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 toggle, autoscale, export, live mode, violation toggle. +- **DashboardToolbar**: Live toggle, edit mode, save/export, info button, reset. +- **NavigatorOverlay**: Minimap with draggable zoom rectangle for `SensorDetailPlot`. +- **HoverCrosshair**: A vertical crosshair that tracks the mouse cursor and shows a multi-line datatip (all visible line values interpolated at the cursor’s x‑position). Activated automatically on `FastSense` plots unless `HoverCrosshair` is set to `false`. +- **openLoupe()**: Creates a standalone enlarged copy of the current plot in a new figure, preserving zoom state. ### 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. diff --git a/wiki/Dashboard-Engine-Guide.md b/wiki/Dashboard-Engine-Guide.md index 2fff83a1..8f30be0b 100644 --- a/wiki/Dashboard-Engine-Guide.md +++ b/wiki/Dashboard-Engine-Guide.md @@ -2,28 +2,34 @@ # 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. --- ## Overview -FastSense provides two dashboard systems: - -| Feature | FastSenseGrid | DashboardEngine | -|---------|---------------|-----------------| -| Grid | Fixed rows x cols | 24-column responsive | -| Tile content | FastSense instances only | 8 widget types (plots, gauges, numbers, tables, etc.) | -| Persistence | None | JSON save/load + .m script export | -| Visual editor | No | Yes (drag/resize, palette, properties panel) | -| Scrolling | No | Auto-scrollbar when content overflows | -| Global time | No | Dual sliders controlling all widgets | -| Sensor binding | Via addSensor per tile | Direct widget property (auto-title, auto-units) | -| Live mode | Per-figure timer | Engine-level timer refreshing all widgets | - -**When to use FastSenseGrid:** You need a simple tiled grid of FastSense time series plots with linked axes and a toolbar. - -**When to use DashboardEngine:** You need mixed widget types (gauges, KPIs, tables, timelines), JSON persistence, or the visual editor. +DashboardEngine provides a full-featured dashboard framework built on a 24‑column responsive grid. +It supports 20+ widget types (gauges, big numbers, time‑series plots, tables, event timelines, custom axes, heatmaps, histograms, images, and more), live data refresh via a timer, global time‑range controls, and a drag‑and‑drop visual editor. + +Dashboards are serializable to JSON (via `save`/`load`) and exportable as self‑contained `.m` scripts. + +| Feature | DashboardEngine | +| ------------------------ | ---------------------------------------- | +| Grid | 24‑column responsive | +| Tile content | 20+ widget types | +| Persistence | JSON save/load + `.m` script export | +| Visual editor | Yes (drag/resize, palette, properties) | +| Scrolling | Auto‑scrollbar when content overflows | +| Global time | Dual sliders controlling all widgets | +| Sensor binding | Direct widget property (auto‑title, units, thresholds) | +| Live mode | Engine‑level timer refreshing all widgets| +| Multi‑page | Named pages with tab‑style navigation | +| Grouping | Panel, collapsible, and tabbed groups | +| Detach | Pop a widget into a standalone window | +| Event overlay | Per‑widget event markers and slider markers | + +**When to choose DashboardEngine:** +You need mixed widget types, persistent dashboards, a visual editor, or a full‑featured monitoring interface. --- @@ -34,7 +40,7 @@ install; % Create some data x = linspace(0, 100, 10000); -y = sin(x) + 0.1 * randn(size(x)); +y = sin(x) + 0.1*randn(size(x)); % Build a dashboard d = DashboardEngine('My First Dashboard'); @@ -55,35 +61,65 @@ 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 24 4] % Full width, 4 rows tall [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. +If a new widget would overlap an existing one, the layout system **pushes it down** to the next free row. Scrollbars appear automatically when the total rows exceed the viewport height. --- ## Widget Types -### FastSense (time series) +DashboardEngine supports the following widget types (use the string name in `addWidget(type, ...)`: + +| Type string | Widget class | Description | +| -------------------- | --------------------------- | ----------- | +| `'fastsense'` | FastSenseWidget | Time‑series plot (wraps FastSense) | +| `'number'` | NumberWidget | Large KPI number with trend arrow | +| `'status'` | StatusWidget | Colored health indicator dot | +| `'gauge'` | GaugeWidget | Arc, donut, bar, or thermometer gauge | +| `'text'` | TextWidget | Static label or section header | +| `'table'` | TableWidget | Tabular data via `uitable` | +| `'rawaxes'` | RawAxesWidget | Custom MATLAB axes | +| `'timeline'` | EventTimelineWidget | Event bars on a timeline | +| `'group'` | GroupWidget | Container for other widgets (panel/collapsible/tabbed) | +| `'divider'` | DividerWidget | Horizontal visual separator | +| `'heatmap'` | HeatmapWidget | Heatmap display | +| `'histogram'` | HistogramWidget | Histogram plot | +| `'barchart'` | BarChartWidget | Bar chart (vertical/horizontal/stacked) | +| `'image'` | ImageWidget | PNG/JPG image or custom image matrix | +| `'scatter'` | ScatterWidget | Scatter plot of two sensors | +| `'chipbar'` | ChipBarWidget | Compact row of status chips | +| `'iconcard'` | IconCardWidget | Mushroom‑style icon/value/label card | +| `'sparkline'` | SparklineCardWidget | KPI card with mini sparkline and delta | +| `'multistatus'` | MultiStatusWidget | Grid of multiple status dots | + +You can also add any widget directly as an object: + +```matlab +d.addWidget(SomeWidget('Title', 'Custom')); +``` + +### FastSense (time‑series) ```matlab -% Sensor-bound (recommended) +% Sensor‑bound (recommended) d.addWidget('fastsense', 'Position', [1 1 12 8], 'Sensor', mySensor); % Inline data @@ -99,7 +135,8 @@ 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 and resolved violations are shown. +The widget title, X‑axis label (`'Time'`), and Y‑axis label (sensor Units or Name) are auto‑derived. ### Number (big value display) @@ -108,18 +145,19 @@ 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. Layout: `[Title | Value+Trend | Units]`. +Shows a large number with a trend arrow (up/down/flat) computed from recent data. +Layout: `[Title | Value+Trend | Units]`. The callback can return a scalar or a struct with fields `value`, `unit`, `trend`. ### Status (health indicator) @@ -134,9 +172,9 @@ d.addWidget('status', 'Title', 'System', ... 'StaticStatus', 'ok'); % 'ok', 'warning', 'alarm' ``` -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 sensor’s latest value. Status is derived automatically from threshold rules when a Sensor or Threshold is given. -### Gauge (arc/donut/bar/thermometer) +### Gauge (arc / donut / bar / thermometer) ```matlab d.addWidget('gauge', 'Title', 'Flow Rate', ... @@ -151,9 +189,8 @@ d.addWidget('gauge', 'Title', 'Efficiency', ... 'Style', 'arc'); ``` -Styles: `'arc'` (default), `'donut'`, `'bar'`, `'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) @@ -178,7 +215,7 @@ d.addWidget('table', 'Title', 'Recent Data', ... 'Position', [1 9 12 4], ... 'Sensor', sTemp, 'N', 15); -% Dynamic data via callback +% Dynamic callback d.addWidget('table', 'Title', 'Live Log', ... 'Position', [1 13 12 4], ... 'DataFcn', @() getRecentAlarms(), ... @@ -199,14 +236,14 @@ d.addWidget('rawaxes', 'Title', 'Temperature Distribution', ... 'PlotFcn', @(ax) histogram(ax, tempData, 50, ... 'FaceColor', [0.31 0.80 0.64], 'EdgeColor', 'none')); -% Sensor-bound with time range +% 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. +`PlotFcn` receives the axes as first argument. When Sensor‑bound, it also receives the Sensor object and optionally a time range. ### Event Timeline @@ -231,11 +268,58 @@ d.addWidget('timeline', 'Title', 'Temp Events', ... 'FilterSensors', {'T-401', 'T-402'}); ``` +### Additional Widgets + +**DividerWidget** – horizontal line for visual grouping. +```matlab +d.addWidget('divider', 'Position', [1 5 24 1], 'Thickness', 2); +``` + +**BarChartWidget** – vertical or horizontal bar chart. +```matlab +d.addWidget('barchart', 'Title', 'Qty by SKU', ... + 'Position', [1 7 12 6], ... + 'DataFcn', @() struct('categories', {{'A','B','C'}}, 'values', [45 70 30])); +``` + +**ChipBarWidget** – compact row of colored status chips. +```matlab +w = ChipBarWidget('Title', 'System Health', 'Position', [1 1 24 1]); +w.Chips = { + struct('label', 'Pump', 'sensor', sPump), + struct('label', 'Tank', 'sensor', sTank), + struct('label', 'Fan', 'statusFcn', @() 'warn') +}; +d.addWidget(w); +``` + +**IconCardWidget** – Mushroom‑style card (icon, value, label). +```matlab +d.addWidget('iconcard', 'Title', 'Temp', ... + 'Position', [1 3 6 3], ... + 'StaticValue', 23.5, 'Units', 'degC'); +``` + +**SparklineCardWidget** – KPI card with sparkline and delta. +```matlab +d.addWidget('sparkline', 'Title', 'CPU', ... + 'Position', [1 5 6 3], ... + 'StaticValue', 42.0, 'SparkData', randn(1,100), 'Units', '%'); +``` + +**MultiStatusWidget** – grid of multiple sensor status dots. +```matlab +d.addWidget('multistatus', 'Position', [1 7 12 3], ... + 'Sensors', {s1, s2, s3, s4, s5}); +``` + +**HeatmapWidget**, **HistogramWidget**, **ImageWidget**, **ScatterWidget** – follow the same pattern with appropriate `DataFcn`/`File`/`Sensor` properties. + --- ## 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: +The recommended way to drive dashboard widgets is through **Sensor objects**. Sensors carry data, state channels, and threshold rules. When you bind a widget to a Sensor, many properties are auto‑derived. ```matlab % Create and configure sensor @@ -254,20 +338,22 @@ 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]); ``` -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 +**Benefits of Sensor binding:** +- **Title** – auto‑derived from `Sensor.Name` or `Sensor.Key` +- **Units** – auto‑derived from `Sensor.Units` +- **Value** – `Sensor.Y(end)` for number, gauge, status +- **Thresholds** – FastSenseWidget renders resolved thresholds and violations +- **Status** – StatusWidget checks latest value against all threshold rules +- **Live refresh** – `refresh()` re‑reads sensor data + +**Legacy** binding via `'Sensor'` name‑value pair is aliased to a Tag property under the hood; for new code, passing the Sensor directly is preferred. --- @@ -279,7 +365,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, and each widget’s type, title, position, and data source. ### Load from JSON @@ -288,7 +374,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,103 +388,222 @@ 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 that, when executed, recreates the dashboard (including multi‑page structures, groups, and collapsible containers). --- ## 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'`, `'dark'`. Legacy names (`'default'`, `'industrial'`, `'scientific'`, `'ocean'`) are aliased to `'light'` for backward compatibility. -You can also override specific theme properties: +**Overriding specific theme properties:** ```matlab theme = DashboardTheme('dark', 'WidgetBackground', [0.1 0.1 0.2]); d.Theme = theme; ``` +You can also assign theme fields after creation: +```matlab +d.Theme.WidgetBackground = [0.1 0.1 0.2]; +``` + +To force a theme re‑render without rebuilding the whole dashboard, call: +```matlab +d.rerenderWidgets(); +``` + +Every widget can have a **per‑widget theme override** via the `ThemeOverride` property (a struct merged on top of the dashboard theme). Not all widgets use all fields, but the base class makes it available. + --- ## Live Mode -DashboardEngine supports live data updates via a timer that periodically calls `refresh()` on all widgets. +DashboardEngine can poll data sources at a fixed interval and refresh all widgets automatically. ```matlab d = DashboardEngine('Live Monitor'); d.Theme = 'dark'; -d.LiveInterval = 2; % refresh every 2 seconds +d.LiveInterval = 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 +d.startLive(); % starts the timer % ... later -d.stopLive(); % stop +d.stopLive(); % stops the timer ``` -You can also toggle live mode from the toolbar's Live button. The toolbar shows the last update timestamp when live mode is active. +**Live button in the toolbar** toggles live mode on/off. When active, the button is highlighted with a blue border. The toolbar also shows the last update timestamp. + +If a widget’s data timestamp fails to advance (stale data), a **stale‑data banner** appears at the top of the dashboard listing the affected widgets. It can be dismissed manually and will re‑appear on the next tick if the situation persists. + +Tick errors do not stop the timer — the engine logs the error and continues (see `onLiveTimerError`). --- ## Global Time Controls -The time panel at the bottom of the dashboard has two sliders that control the visible time range across all widgets. Moving the sliders calls `setTimeRange(tStart, tEnd)` on each widget. +The **time panel** at the bottom of the dashboard has two sliders that control the visible time range across **all widgets on all pages**. -- **FastSenseWidget:** sets xlim on the FastSense axes -- **EventTimelineWidget:** sets xlim on the timeline axes -- **RawAxesWidget:** passes the time range to the PlotFcn +- Moving the sliders calls `setTimeRange(tStart, tEnd)` on each widget. +- **FastSenseWidget** adjusts its `xlim` (preserving zoom state if the widget is detached from global time). +- **EventTimelineWidget** and **RawAxesWidget** adjust accordingly. +- Widgets can detach from global time when the user manually zooms (`UseGlobalTime = false`). -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. +**Sync button** (refresh icon in toolbar) re‑attaches all widgets to global time and resets to the full data extent. + +**Preview envelope** – in the time slider background, a faint min/max envelope of all widget data (and event markers) is drawn, giving an overview of data distribution across time. --- ## Visual Editor -Click the **Edit** button in the toolbar to enter edit mode: +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 +- A **palette sidebar** appears on the left with buttons for each widget type. +- A **properties panel** appears on the right showing the selected widget’s settings (title, position, data source, etc.). +- **Drag handles** let you reposition widgets on the grid. +- **Resize handles** let you change widget dimensions (snapping to the 24‑column grid). +- Click **Apply** to save property changes, **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. +Programmatic widget management: +```matlab +d.addWidget('text', 'Title', 'New', 'Position', [1 1 6 2]); +d.deleteWidget(idx); % remove widget by index +d.setWidgetPosition(idx, [1 3 8 2]); % move/resize +``` -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 +The editor also supports multi‑page and group widgets: you can add pages, and inside groups you can drag children within the group’s sub‑grid. --- ## Info File Integration -Dashboards can link to external Markdown documentation files: +Dashboards can link to an external Markdown documentation file: ```matlab -d = DashboardEngine('My Dashboard'); -d.InfoFile = 'dashboard_help.md'; % path to Markdown file +d.InfoFile = 'dashboard_help.md'; % path to Markdown 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 as HTML (via `MarkdownRenderer`) and opens it in the system browser. Alternatively, a temporary HTML file is created and launched via `web()`, or, on newer MATLAB, in an in‑app modal. + +If no `InfoFile` is set, clicking the Info button displays a placeholder page that explains how to attach custom documentation. + +--- + +## Multi‑Page Dashboards + +You can split a dashboard into named pages with tab‑style navigation. + +```matlab +d = DashboardEngine('Multi‑Page Example'); + +% Page 1 +d.addPage('Overview'); +d.addWidget('text', 'Title', 'Welcome', 'Position', [1 1 24 1], ... + 'Content', 'Main view'); +d.addWidget('fastsense', 'Sensor', sTemp, 'Position', [1 3 24 8]); + +% Page 2 +d.addPage('Details'); +d.addWidget('table', 'Title', 'Raw Data', 'Position', [1 1 24 8], ... + 'Sensor', sTemp, 'N', 50); +d.addWidget('gauge', 'Title', 'Temp', 'Position', [1 9 8 6], 'Sensor', sTemp); + +d.render(); +``` + +When `addPage()` is called, subsequent `addWidget()` calls are routed to that page until you switch back (`switchPage(idx)` or via the editor). The toolbar automatically shows page tabs above the content area. + +Each page is a `DashboardPage` object holding a list of widgets. The layout system reflows all widgets across pages (hiding inactive pages) and updates scrollbars. + +--- + +## Grouping and Layout Containers + +`GroupWidget` allows you to embed other widgets inside a panel, collapsible region, or tabbed container. + +**Panel mode** (default) – groups children under a colored header. + +```matlab +d.addWidget('group', 'Label', 'Sensor Suite', ... + 'Children', {w1, w2, w3}); +``` + +**Collapsible mode** – header with collapse/expand toggle. + +```matlab +collapsed = d.addCollapsible('Advanced Settings', {wA, wB}); +collapsed.collapse(); % collapse programmatically +collapsed.expand(); +``` + +**Tabbed mode** – children organized under tabs. + +```matlab +dg = GroupWidget('Mode', 'tabbed', 'Label', 'Views'); +dg.addChild(wPlot, 'Chart'); +dg.addChild(wTable, 'Table'); +d.addWidget(dg); +``` + +Groups can be nested, though depth is limited (the engine enforces a maximum nesting depth via `addChild` checks). Group children are laid out automatically in a sub‑grid (`ChildColumns`, `ChildAutoFlow`). + +When a collapsible group expands or collapses, the parent layout reflows automatically. + +--- + +## Detaching Widgets + +You can pop any widget into a standalone figure window that remains live‑mirrored (updated by the engine’s live timer). + +```matlab +d.detachWidget(d.getWidgetByTitle('Temperature')); +``` + +This creates a `DetachedMirror` object, clones the widget (via `toStruct`/`fromStruct`), and renders it in a separate figure. The mirror is kept in sync during live ticks. Closing the detached window cleans up the mirror automatically. + +--- + +## Progress Bar and Stale Banner + +**Progress bar** – during long renders (many widgets), a self‑updating progress line is printed to the console. You can control its visibility with: + +```matlab +d.ProgressMode = 'on'; % 'auto' (default), 'on', 'off' +``` + +**Stale banner** – if, during live mode, a widget’s time range fails to advance, a warning banner appears listing the stale widget titles. This helps spot sensor connectivity issues. The banner’s reserved space is always at the top of the figure, independent of toolbar/page height. + +--- + +## Export Image + +Capture the current dashboard figure as a PNG or JPEG at 150 DPI. + +```matlab +d.exportImage('dashboard.png'); +d.exportImage('dashboard.jpg', 'jpeg'); +``` + +The default filename is `{dashboard name}_{timestamp}.png`. The toolbar **Image** button invokes a save dialog. --- ## Complete Example -This example creates a process monitoring dashboard with sensor-bound widgets: +This example creates a process monitoring dashboard with sensor‑bound widgets, multi‑page, and live mode. ```matlab install; @@ -406,7 +611,7 @@ install; %% Generate data rng(42); N = 10000; -t = linspace(0, 86400, N); % 24 hours +t = linspace(0, 86400, N); % 24 hours in seconds % Machine mode state channel scMode = StateChannel('machine'); @@ -439,7 +644,8 @@ d = DashboardEngine('Process Monitoring — Line 4'); d.Theme = 'light'; d.LiveInterval = 5; -% Header row: text + numbers + status +% Page 1 — Overview +d.addPage('Overview'); 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], ... @@ -451,14 +657,14 @@ d.addWidget('status', 'Title', 'Temp', 'Position', [15 1 5 2], ... 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], ... +% Page 2 — Details +d.addPage('Details'); +d.addWidget('gauge', 'Title', 'Pressure', 'Position', [1 1 8 6], ... 'Sensor', sPress, 'Range', [0 100], 'Units', 'psi'); -d.addWidget('rawaxes', 'Title', 'Temp Distribution', 'Position', [9 11 8 6], ... +d.addWidget('rawaxes', 'Title', 'Temp Distribution', 'Position', [9 1 8 6], ... 'PlotFcn', @(ax) histogram(ax, sTemp.Y, 50, ... 'FaceColor', [0.31 0.80 0.64], 'EdgeColor', 'none')); @@ -470,9 +676,21 @@ d.save(fullfile(tempdir, 'process_dashboard.json')); --- +## Tips and Gotchas + +- **Widget order matters** – when adding widgets that share grid space, add them in the order you want them stacked (first‑added gets priority). The layout resolves overlaps by pushing later widgets down. +- **Sensor resolution** – when loading from JSON, Sensor bindings are lost unless you provide a `SensorResolver` function that maps Saved Sensor names back to live objects. +- **Group nesting depth** – the engine enforces a maximum nesting depth for GroupWidget children to prevent infinite recursion. +- **Live mode and manual zoom** – manually zooming a FastSense widget detaches it from global time so that it won’t scroll with the slider. Use the Sync button to re‑attach. +- **Event markers** – the toolbar **Events** button toggles a global event‑marker overlay on all widgets that support it (FastSenseWidget, RawAxesWidget). This state is not persisted. +- **ProgressMode** – for batch scripts or CI, set to `'off'` to keep output clean. +- **DetachedMirrors** – they are automatically added to the engine’s lifecycle; closing the window calls `removeDetached()` to avoid stale handles. + +--- + ## 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_dashboard_engine`, `example_dashboard_all_widgets` diff --git a/wiki/Datetime-Guide.md b/wiki/Datetime-Guide.md index d14472cf..10b79db8 100644 --- a/wiki/Datetime-Guide.md +++ b/wiki/Datetime-Guide.md @@ -2,17 +2,21 @@ # Datetime Guide -FastSense supports time series data with datetime X-axes. Both datenum values and MATLAB datetime objects are supported. +FastSense fully supports time series with datetime X-axes. You can pass +`datenum` values directly, or MATLAB `datetime` objects (which are +automatically converted to `datenum`). Tick labels, crosshairs, and data +cursors all render in human-readable date/time format. --- -## Using datenum +## Using `datenum` -Pass datenum values as X data with `'XType', 'datenum'`: +Pass numeric `datenum` values as X data, explicitly setting `'XType'` to +`'datenum'`: ```matlab -% Generate datenum time stamps -x = datenum(2024, 1, 1) + (0:99999) / 86400; % 1-second resolution, ~1 day +% Generate datenum timestamps — 1-second resolution, ~1 day of data +x = datenum(2024, 1, 1) + (0:99999) / 86400; y = sin(2 * pi * (1:100000) / 86400) + 0.1 * randn(1, 100000); fp = FastSense('Theme', 'dark'); @@ -20,11 +24,15 @@ fp.addLine(x, y, 'XType', 'datenum', 'DisplayName', 'Daily Cycle'); fp.render(); ``` +The `'XType'` parameter tells FastSense that the X values are numeric dates +and triggers human-readable formatting. + --- -## Using MATLAB datetime (auto-detected) +## Using MATLAB `datetime` (auto‑detected) -In MATLAB (not Octave), you can pass datetime objects directly — they are auto-converted to datenum: +In MATLAB (not Octave), you can pass `datetime` objects directly. +They are automatically converted to `datenum` and the X‑type is set for you: ```matlab dt = datetime(2024, 1, 1) + hours(0:9999); @@ -35,30 +43,31 @@ fp.addLine(dt, y, 'DisplayName', 'Sensor'); fp.render(); ``` -The `XType` is set automatically to 'datenum' and `IsDatetime` becomes true. +No `'XType'` flag is needed — it is inferred automatically, and an internal +`IsDatetime` flag is set to `true`. --- -## Auto-Formatting Tick Labels +## Auto‑Formatted Tick Labels -Tick labels automatically adapt to the visible zoom level: +Tick labels on the X‑axis adapt dynamically to the visible zoom level: -| Visible Range | Format | Example | -|--------------|--------|---------| -| > 1 day | `mmm dd HH:MM` | Jan 15 10:00 | -| 1 hour – 1 day | `HH:MM` | 10:00 | -| < 1 minute | `HH:MM:SS` | 10:30:15 | +| Visible Range | Format | Example | +|---------------------|-----------------|---------------| +| > 1 day | `mmm dd HH:MM` | Jan 15 10:00 | +| 1 hour – 1 day | `HH:MM` | 10:00 | +| < 1 minute | `HH:MM:SS` | 10:30:15 | -As you zoom in, tick labels progressively show more precision. Zoom out and they show dates. +As you zoom in, labels show more precision; zoom out and they show dates. --- ## Datetime with Thresholds -Thresholds work the same way with datetime data: +Thresholds work identically with datetime data: ```matlab -x = datenum(2024, 1, 1) + (0:999999) / 86400; % ~11.5 days +x = datenum(2024, 1, 1) + (0:999999) / 86400; % ~11.5 days y = randn(1, 1000000) * 5 + 50; fp = FastSense('Theme', 'industrial'); @@ -68,21 +77,28 @@ fp.addThreshold(40, 'Direction', 'lower', 'ShowViolations', true, 'Label', 'Low' fp.render(); ``` +For time‑varying thresholds, pass `datenum` X vectors as the first two arguments +to [`addThreshold`](API Reference: FastPlot). + --- ## Datetime with Dashboard +Combine datetime axes with `FastSenseGrid` tiles: + ```matlab x = datenum(2024, 1, 1) + (0:999999) / 86400; fig = FastSenseGrid(2, 1, 'Theme', 'dark'); fp1 = fig.tile(1); -fp1.addLine(x, sin(2*pi*(1:1e6)/86400)*20+50, 'XType', 'datenum', 'DisplayName', 'Pressure'); +fp1.addLine(x, sin(2*pi*(1:1e6)/86400)*20 + 50, ... + 'XType', 'datenum', 'DisplayName', 'Pressure'); fig.setTileTitle(1, 'Pressure'); fp2 = fig.tile(2); -fp2.addLine(x, cos(2*pi*(1:1e6)/86400)*10+25, 'XType', 'datenum', 'DisplayName', 'Temperature'); +fp2.addLine(x, cos(2*pi*(1:1e6)/86400)*10 + 25, ... + 'XType', 'datenum', 'DisplayName', 'Temperature'); fig.setTileTitle(2, 'Temperature'); fig.renderAll(); @@ -92,7 +108,8 @@ fig.renderAll(); ## Datetime with Linked Axes -Linked axes work with datetime — synchronized zoom/pan shows consistent time ranges: +When multiple plots share a `LinkGroup`, zooming and panning stay synchronized, +showing consistent time ranges: ```matlab fig = figure; @@ -108,58 +125,40 @@ fp2.addLine(x, temperature, 'XType', 'datenum', 'DisplayName', 'Temperature'); fp2.render(); ``` +All linked plots respond as one when you navigate any of them. + --- ## Toolbar with Datetime -The crosshair and data cursor display datetime values in human-readable format when XType is 'datenum': +The `FastSenseToolbar` crosshair and data cursor display X values in a +human‑readable datetime format whenever `XType` is `'datenum'`: ```matlab fp = FastSense('Theme', 'dark'); fp.addLine(x, y, 'XType', 'datenum'); fp.render(); + tb = FastSenseToolbar(fp); -% Crosshair shows: "Jan 15, 2024 10:30:15 Y: 52.3" +% Clicking the crosshair shows something like: "Jan 15, 2024 10:30:15 Y: 52.3" ``` -The [[API Reference: FastPlot|FastSenseToolbar]] provides `formatX()` for consistent datetime formatting. +The static method `FastSenseToolbar.formatX(xVal, xType)` provides the +formatting logic. See the [API Reference: FastPlot](API Reference: FastPlot) +for details. --- -## Sensor Data with Datetime +## `SensorDetailPlot` with Datetime -Sensor X data is typically in datenum format: +The [`SensorDetailPlot`](SensorDetailPlot) component accepts an `'XType'` +parameter to enable datetime axes: ```matlab -s = Sensor('pressure', 'Name', 'Chamber Pressure'); -s.X = datenum(2024, 1, 1) + (0:999999) / 86400; -s.Y = randn(1, 1000000) * 10 + 50; - -sc = StateChannel('machine'); -sc.X = datenum(2024, 1, 1) + [0 3 7 10]; % Day boundaries -sc.Y = [0 1 2 1]; -s.addStateChannel(sc); - -s.addThresholdRule(struct('machine', 1), 70, 'Direction', 'upper', 'Label', 'Run HI'); -s.resolve(); - -fp = FastSense('Theme', 'dark'); -fp.addSensor(s, 'ShowThresholds', true); -fp.render(); -``` - ---- - -## SensorDetailPlot with Datetime - -The `SensorDetailPlot` component supports datetime X-axes through the `'XType'` parameter: - -```matlab -% Create sensor with datenum timestamps +% Create sensor data with datenum timestamps tStart = datetime(2024, 3, 11, 8, 0, 0); -tEnd = datetime(2024, 3, 11, 10, 0, 0); -tDatetime = linspace(tStart, tEnd, 72000); -tNum = datenum(tDatetime); +tEnd = datetime(2024, 3, 11, 10, 0, 0); +tNum = datenum(linspace(tStart, tEnd, 72000)); s = Sensor('pressure', 'Name', 'Line Pressure'); s.X = tNum; @@ -171,22 +170,23 @@ sdp = SensorDetailPlot(s, 'XType', 'datenum', 'Theme', 'light'); sdp.render(); ``` -The navigator and main plot both show human-readable time labels. +Both the navigator and the main plot show human‑readable time labels. --- ## Large Dataset Example -FastSense handles massive datetime datasets efficiently: +FastSense handles massive datetime datasets efficiently using dynamic +downsampling: ```matlab % ~579 days of temperature data at 1-second resolution n = 50000000; -x = datenum(2024,1,1) + (0:n-1)/86400; % ~579 days -t = (0:n-1) / 86400; % time in days -y = 20 + 5*sin(t * 2*pi - pi/2) + ... % daily cycle (peak at midday) - 0.3*sin(t * 2*pi*24) + ... % hourly ripple - 0.1*randn(1,n); % sensor noise +x = datenum(2024, 1, 1) + (0:n-1)/86400; % ~579 days +t = (0:n-1) / 86400; % time in days +y = 20 + 5*sin(t*2*pi - pi/2) + ... % daily cycle (peak at midday) + 0.3*sin(t*2*pi*24) + ... % hourly ripple + 0.1*randn(1, n); % sensor noise fp = FastSense('Theme', 'light'); fp.addLine(x, y, 'DisplayName', 'Temperature', 'XType', 'datenum'); @@ -194,30 +194,43 @@ fp.addThreshold(24, 'Direction', 'upper', 'ShowViolations', true); fp.render(); ``` +The pyramid cache ensures that zoom and pan remain responsive even with tens of +millions of points. + --- ## GNU Octave Notes -- Octave does not support MATLAB's `datetime` class -- Use `datenum()` directly for time stamps -- Always pass `'XType', 'datenum'` explicitly -- Tick label formatting works the same way +- Octave does not support MATLAB’s `datetime` class. +- Use `datenum()` directly for timestamps and always pass `'XType', 'datenum'`. +- Tick label auto‑formatting works the same as in MATLAB. + +```matlab +x = datenum(2024, 1, 1) + (0:99999) / 86400; +fp = FastSense(); +fp.addLine(x, y, 'XType', 'datenum'); +fp.render(); +``` --- ## Tips -- All X data in a single FastSense must be the same type (all numeric or all datenum) -- For high-frequency data (kHz+), datenum precision is sufficient (double-precision days) -- Use `datenum()` for generating time stamps: `datenum(year, month, day, hour, min, sec)` -- Use `datestr()` for converting back: `datestr(x(1), 'yyyy-mm-dd HH:MM:SS')` -- Datetime formatting automatically adapts to zoom level — no manual intervention needed -- The [[API Reference: FastPlot|FastSenseToolbar]] crosshair and data cursor show formatted datetime strings +- All X data in a single `FastSense` instance must share the same X‑type + (all numeric or all `datenum`). Mixing is not supported. +- For high‑frequency data (kHz+), `datenum` precision (double‑precision days) is + more than adequate — sub‑microsecond resolution is possible. +- Use `datenum(year, month, day, hour, minute, second)` to build timestamps. +- Use `datestr(x(1), 'yyyy-mm-dd HH:MM:SS')` to convert back for inspection. +- Tick label formatting adapts automatically to the zoom level; no manual + intervention is required. +- The [`FastSenseToolbar`](API Reference: FastPlot) crosshair and data cursor + display formatted datetime strings via `FastSenseToolbar.formatX`. --- ## See Also -- [[API Reference: FastPlot]] — addLine() with XType parameter -- [[API Reference: Sensors]] — Sensor X data -- [[Examples]] — example_datetime.m, example_sensor_detail_datetime.m +- [API Reference: FastPlot](API Reference: FastPlot) — `addLine()` with the `XType` parameter +- [FastSenseToolbar](API Reference: FastPlot) — `formatX()` static method +- [Examples](Examples) — `example_datetime.m`, `example_sensor_detail_datetime.m` diff --git a/wiki/Live-Mode-Guide.md b/wiki/Live-Mode-Guide.md index 0e25d85a..ef99220b 100644 --- a/wiki/Live-Mode-Guide.md +++ b/wiki/Live-Mode-Guide.md @@ -2,7 +2,7 @@ # Live Mode Guide -FastSense supports live data visualization by polling a .mat file for updates and auto-refreshing the display. Live mode works with single plots, tiled dashboards, and tabbed docks. +FastSense supports live data visualization by polling a `.mat` file for updates and auto‑refreshing the display. Live mode works with single plots, tiled dashboards (`FastSenseGrid`), and tabbed docks (`FastSenseDock`). --- @@ -25,8 +25,10 @@ fp.startLive('data.mat', @(fp, s) fp.updateData(1, s.x, s.y)); The callback `@(fp, s) fp.updateData(1, s.x, s.y)` is called every poll cycle: - `fp` — the FastSense instance -- `s` — struct loaded from the .mat file -- `fp.updateData(lineIdx, newX, newY)` — replaces line data and re-renders +- `s` — struct loaded from the `.mat` file +- `fp.updateData(lineIdx, newX, newY)` replaces line data and re-renders + +By default, the plot is polled every 2 seconds (see the `LiveInterval` property). When the file’s modification time changes, the new data is loaded and the display updates. ### Stopping Live Mode @@ -34,7 +36,7 @@ The callback `@(fp, s) fp.updateData(1, s.x, s.y)` is called every poll cycle: fp.stopLive(); ``` -Or use the Live Mode button in the toolbar. +Or use the **Live Mode** button in the [[FastSenseToolbar]] (see [Toolbar Integration](#toolbar-integration)). --- @@ -42,17 +44,18 @@ Or use the Live Mode button in the toolbar. Control how the view updates when new data arrives: -| Mode | Behavior | -|------|----------| -| `'preserve'` | Keep current zoom/pan position. User's view is not disturbed | -| `'follow'` | Scroll X-axis to show the latest data. Good for monitoring | -| `'reset'` | Zoom to show all data. Good for overview | +| Mode | Behavior | +|------------|----------------------------------------------------------------| +| `'preserve'` | Keep current zoom/pan position. User’s view is not disturbed. | +| `'follow'` | Scroll the X‑axis to show the latest data. Ideal for monitoring. | +| `'reset'` | Zoom to show all data. Good for overview. | ```matlab fp.startLive('data.mat', @updateFcn, 'ViewMode', 'follow'); ``` Change view mode while running: + ```matlab fp.setViewMode('follow'); fp.setViewMode('preserve'); @@ -62,18 +65,25 @@ fp.setViewMode('preserve'); ## Polling Interval -Default is 2 seconds. Adjust with the 'Interval' option: +Default is 2 seconds. Adjust with the `'Interval'` option: + +```matlab +fp.startLive('data.mat', @updateFcn, 'Interval', 0.5); % poll every 500 ms +fp.startLive('data.mat', @updateFcn, 'Interval', 5); % poll every 5 s +``` + +You can also set the `LiveInterval` property before calling `startLive`: ```matlab -fp.startLive('data.mat', @updateFcn, 'Interval', 0.5); % Poll every 500ms -fp.startLive('data.mat', @updateFcn, 'Interval', 5); % Poll every 5 seconds +fp.LiveInterval = 1.5; +fp.startLive('data.mat', @updateFcn); ``` --- ## Live Dashboard -FastSenseGrid supports live mode across all tiles: +`FastSenseGrid` supports live mode across all tiles: ```matlab fig = FastSenseGrid(2, 2, 'Theme', 'dark'); @@ -90,7 +100,8 @@ fig.startLive('sensors.mat', @(fig, s) updateDashboard(fig, s), ... 'Interval', 2, 'ViewMode', 'follow'); ``` -Update callback for dashboard: +Update callback for the dashboard: + ```matlab function updateDashboard(fig, s) fig.tile(1).updateData(1, s.t, s.pressure); @@ -100,55 +111,40 @@ function updateDashboard(fig, s) end ``` +The `fig.startLive` method creates a single timer that polls the file and calls the update function; each tile’s line is updated independently. + --- ## Live Mode with Metadata -Attach metadata that updates on each poll: +You can attach metadata (extra fields like units, calibration date, etc.) that updates with every poll. + +Metadata is loaded from a **separate** `.mat` file. Set the `MetadataFile`, `MetadataVars`, and line/tile indices **before** starting live mode: ```matlab -fp.startLive('data.mat', @updateFcn, ... - 'MetadataFile', 'meta.mat', ... - 'MetadataVars', {'units', 'calibration'}); +fp = FastSense(...); +% ... add lines, render ... + +fp.MetadataFile = 'meta.mat'; +fp.MetadataVars = {'units', 'calibration'}; +fp.MetadataLineIndex = 1; % which line within the plot gets the metadata +fp.startLive('data.mat', @updateFcn); ``` -The metadata is loaded from a separate file and attached to the specified line and tile: +Now, on every refresh, the variables `units` and `calibration` are loaded from `meta.mat` and attached to the specified line. You can then use `lookupMetadata` to retrieve the metadata at any point: ```matlab -fig.MetadataFile = 'metadata.mat'; -fig.MetadataVars = {'sensor_id', 'location', 'units'}; -fig.MetadataLineIndex = 1; % which line within the tile -fig.MetadataTileIndex = 1; % which tile to attach to +m = fp.lookupMetadata(1, 3.5); % metadata at x=3.5 for line 1 +disp(m.units) ``` ---- - -## Live Event Detection - -Combine live mode with event detection for real-time monitoring using the LiveEventPipeline: +On a `FastSenseGrid`, you have analogous properties, plus `MetadataTileIndex` to select which tile’s line receives the metadata: ```matlab -% Create sensors with thresholds -tempSensor = Sensor('temperature', 'Name', 'Temperature'); -tempSensor.addThresholdRule(struct(), 78, 'Direction', 'upper', 'Label', 'Hi Warn'); -tempSensor.addThresholdRule(struct(), 82, 'Direction', 'upper', 'Label', 'Hi Alarm'); -tempSensor.resolve(); - -sensors = containers.Map(); -sensors('temperature') = tempSensor; - -% Configure data sources -dsMap = DataSourceMap(); -dsMap.add('temperature', MockDataSource('BaseValue', 70, 'NoiseStd', 2)); - -% Set up pipeline with event store -pipeline = LiveEventPipeline(sensors, dsMap, ... - 'EventFile', 'events.mat', ... - 'Interval', 15, ... - 'MinDuration', 0.5); - -% Start live event detection -pipeline.start(); +fig.MetadataFile = 'meta.mat'; +fig.MetadataVars = {'sensor_id', 'location', 'units'}; +fig.MetadataLineIndex = 1; +fig.MetadataTileIndex = 1; % attach to tile 1’s line ``` --- @@ -159,14 +155,13 @@ GNU Octave does not support MATLAB timers. Use `runLive()` for a blocking poll l ```matlab fp.render(); - -% Blocking loop — press Ctrl+C to stop fp.LiveFile = 'data.mat'; fp.LiveUpdateFcn = @(fp, s) fp.updateData(1, s.x, s.y); -fp.runLive(); +fp.runLive(); % press Ctrl+C to stop ``` For dashboards: + ```matlab fig.renderAll(); fig.LiveFile = 'data.mat'; @@ -181,7 +176,7 @@ fig.runLive(); Trigger a one-shot data reload without starting continuous polling: ```matlab -fp.refresh(); +fp.refresh(); % loads LiveFile, calls LiveUpdateFcn, reloads metadata fig.refresh(); ``` @@ -189,7 +184,7 @@ fig.refresh(); ## Toolbar Integration -The [[FastSenseToolbar|API Reference: FastSenseToolbar]] provides a Live Mode button: +The [[FastSenseToolbar]] provides a **Live Mode** button and a **Refresh** button. ```matlab tb = FastSenseToolbar(fp); @@ -198,43 +193,46 @@ tb = FastSenseToolbar(fp); tb.toggleLive(); ``` -The Refresh button triggers a manual one-shot reload: +The Refresh button triggers `fp.refresh()`: + ```matlab tb.refresh(); ``` +When toolbar is attached to a `FastSenseGrid`, `toggleLive` starts/stops live mode on the whole dashboard. + --- ## Console Progress Bars -Use ConsoleProgressBar for visual feedback during long operations: +`ConsoleProgressBar` is a lightweight utility for visual feedback during long operations (e.g., rendering many dashboards). It is not tied to live mode, but useful for scripts that prepare or refresh data. ```matlab -pb = ConsoleProgressBar(2); % 2-space indent +pb = ConsoleProgressBar(2); % 2‑space indent pb.start(); for k = 1:8 pb.update(k, 8, 'Tile 1'); pause(0.1); end -pb.freeze(); % becomes permanent line +pb.freeze(); % makes the current state permanent (prints newline) ``` --- ## Tips -- Set `'ViewMode', 'follow'` for monitoring use cases where you always want to see the latest data -- Use `'preserve'` when users need to zoom into historical data while live updates continue -- Keep polling interval reasonable (1-5 seconds) to avoid overwhelming the file system -- The .mat file should be written atomically (write to temp file, then rename) to avoid partial reads -- Live mode works with linked axes — all linked plots update together -- Use `DeferDraw = true` to skip drawnow during batch render for better performance +- Set `'ViewMode', 'follow'` for monitoring use cases where you always want to see the latest data. +- Use `'preserve'` when users need to zoom into historical data while live updates continue. +- Keep the polling interval reasonable (1–5 seconds) to avoid overwhelming the file system. +- Write the `.mat` file **atomically** (write to a temp file, then rename) to prevent partial reads. +- Live mode works with linked axes — all linked plots update together when `updateData` is called on any of them. +- Use `DeferDraw = true` to suppress `drawnow` during batch updates (improves performance). +- Metadata variables are loaded from a **separate** `.mat` file and are attached to a specific line; make sure the variable names exist in that file. --- ## See Also -- [[API Reference: FastPlot]] — startLive(), stopLive(), updateData() methods -- [[API Reference: Dashboard]] — Dashboard live mode -- [[API Reference: Event Detection]] — Live event detection -- [[Examples]] — example_dashboard_live.m, example_live_pipeline.m +- [[API Reference: FastPlot]] — `startLive()`, `stopLive()`, `updateData()`, `refresh()`, `setViewMode()` methods and live‑related properties +- [[Dashboard|API Reference: Dashboard]] — Dashboard live mode (`startLive`, `stopLive`, `refresh`) +- [[Examples]] — `example_dashboard_live.m` (if available) diff --git a/wiki/MEX-Acceleration.md b/wiki/MEX-Acceleration.md index 1d99b22c..ab91ee70 100644 --- a/wiki/MEX-Acceleration.md +++ b/wiki/MEX-Acceleration.md @@ -2,7 +2,7 @@ # MEX Acceleration -FastSense includes optional C MEX functions with SIMD intrinsics for maximum performance. All MEX functions have pure-MATLAB fallbacks — behavior is identical. +FastSense includes optional C MEX functions with SIMD intrinsics for maximum performance. All MEX functions have pure-MATLAB fallbacks — behavior is identical. This guide covers building, architecture support, the accelerated functions, and verification. ## Building MEX Files @@ -11,7 +11,7 @@ cd libs/FastSense build_mex(); ``` -The build script auto-detects your architecture and compiles all MEX functions with appropriate SIMD optimizations. +The build script auto‑detects your CPU architecture and compiler, then compiles all MEX functions with the best available SIMD optimizations. If AVX2 fails on x86_64, it automatically retries with SSE2. ### Requirements @@ -23,79 +23,86 @@ The build script auto-detects your architecture and compiles all MEX functions w SQLite3 is bundled as an amalgamation and compiled directly into MEX files that need it — no system installation required. +Before building, an incremental check based on a **mex‑stamp** (computed by `mex_stamp()`) ensures that only changed sources are recompiled; if the stamp matches, `build_mex()` is skipped entirely. + ## Architecture Support -All MEX functions include a common SIMD abstraction layer that adapts to your CPU: +All MEX functions share a common SIMD abstraction layer. The build process selects the best instruction set for your hardware: | Architecture | SIMD Instructions | Fallback | |-------------|------------------|----------| | x86_64 | AVX2 + FMA | SSE2 | -| ARM64 (Apple Silicon) | NEON | - | -| Other | Scalar operations | - | +| ARM64 (Apple Silicon) | NEON | — | +| Other | Scalar operations | — | -If AVX2 compilation fails on x86_64, the build script automatically retries with SSE2. +If AVX2 compilation fails on x86_64, the script automatically retries with `-msse2` so that MEX files still benefit from SSE2 vectorization. ## Accelerated Functions ### Core Downsampling -**binary_search_mex** — O(log n) binary search for visible data range -- **Speedup**: 10-20x over MATLAB's `find` -- **Used by**: Zoom/pan callbacks to locate visible indices +**`binary_search_mex`** — O(log n) binary search for visible data range. +- **Speedup**: 10–20× over MATLAB's `find`. +- **Used by**: Zoom/pan callbacks to locate visible indices. -**minmax_core_mex** — Per-pixel MinMax reduction with SIMD vectorization -- **Speedup**: 3-10x over pure MATLAB -- **SIMD**: Processes 4 doubles (AVX2) or 2 doubles (NEON) per cycle -- **Used by**: Default downsampling algorithm in [[FastPlot|API Reference: FastPlot]] +**`minmax_core_mex`** — Per‑pixel Min‑Max reduction with SIMD vectorization. +- **Speedup**: 3–10× over pure MATLAB. +- **SIMD**: Processes 4 doubles (AVX2) or 2 doubles (NEON) per cycle. +- **Used by**: Default downsampling in [[FastPlot|API Reference: FastPlot]]. -**lttb_core_mex** — Largest Triangle Three Buckets with SIMD triangle area computation -- **Speedup**: 10-50x over MATLAB implementation -- **Used by**: LTTB downsampling method +**`lttb_core_mex`** — Largest Triangle Three Buckets with SIMD triangle area computation. +- **Speedup**: 10–50× over MATLAB implementation. +- **Used by**: LTTB downsampling method. ### Threshold Processing -**violation_cull_mex** — Fused threshold violation detection and pixel culling -- **Speedup**: Significant (single-pass vs two-pass MATLAB) -- **Used by**: Violation marker rendering during zoom/pan +**`violation_cull_mex`** — Fused threshold violation detection and pixel culling. +- **Speedup**: Significant (single‑pass vs two‑pass MATLAB). +- **Used by**: Violation marker rendering during zoom/pan. + +**`compute_violations_mex`** — Batch threshold violation detection. +- **Speedup**: Significant over per‑point MATLAB comparison. +- **Used by**: [[Sensors|API Reference: Sensors]] resolution pipeline. -**compute_violations_mex** — Batch threshold violation detection -- **Speedup**: Significant over per-point MATLAB comparison -- **Used by**: [[Sensors|API Reference: Sensors]] resolution pipeline +**`to_step_function_mex`** — SIMD step‑function conversion for thresholds (converts threshold (X,Y) pairs into a staircase representation). +- **Speedup**: Notable when resolutions involve many threshold segments. +- **Used by**: Sensor resolution logic, especially with time‑varying thresholds. ### Data Storage -**build_store_mex** — Bulk SQLite writer for DataStore initialization -- **Speedup**: 2-3x (eliminates ~20K MATLAB-to-MEX round-trips) -- **SIMD**: Accelerated Y min/max computation per chunk -- **Used by**: `FastSenseDataStore` construction +**`build_store_mex`** — Bulk SQLite writer for DataStore initialization. +- **Speedup**: 2–3× (eliminates ~20K MATLAB‑to‑MEX round‑trips). +- **SIMD**: Accelerated Y min/max computation per chunk. +- **Used by**: `FastSenseDataStore` construction. -**resolve_disk_mex** — SQLite disk-based sensor resolution -- **Used by**: `Sensor.resolve()` with disk-backed storage -- **Benefit**: Reads chunks from database without loading full datasets +**`resolve_disk_mex`** — SQLite disk‑based sensor resolution. +- **Speedup**: Reads chunks from database without loading full datasets. +- **Used by**: `Sensor.resolve()` when data is stored on disk. -**mksqlite** — SQLite3 MEX interface with typed BLOB support -- **Used by**: DataStore, disk-backed sensor resolution -- **Features**: Serializes MATLAB arrays preserving type and shape +**`mksqlite`** — SQLite3 MEX interface with typed BLOB support. +- **Used by**: DataStore, disk‑backed sensor resolution. +- **Features**: Serializes MATLAB arrays preserving type and shape. ## Fallback Behavior When MEX files are unavailable: -- Each function has a pure-MATLAB equivalent in `libs/FastSense/private/` -- Runtime auto-detection switches between MEX and MATLAB seamlessly -- Identical numerical results and API -- Performance remains excellent for datasets under ~10M points +- Each function has a pure‑MATLAB equivalent in `libs/FastSense/private/`. +- Runtime auto‑detection switches between MEX and MATLAB seamlessly. +- Identical numerical results and API. +- Performance remains excellent for datasets under ~10M points. ## Compilation Process The `build_mex()` function: -1. **Detects architecture** — normalizes platform strings (`maca64`, `aarch64`, etc.) into canonical labels -2. **Selects compiler** — prefers GCC on Octave for better auto-vectorization; uses MATLAB's default on MATLAB -3. **Sets SIMD flags** — chooses instruction sets based on detected CPU architecture -4. **Compiles sources** — builds all MEX files with bundled SQLite3 amalgamation -5. **Handles failures** — automatically retries x86_64 builds with SSE2 if AVX2 fails -6. **Copies shared files** — distributes MEX binaries to other library directories +1. **Detects architecture** — normalises platform strings (`maca64`, `aarch64`, `x86_64`, …) +2. **Selects compiler** — on Octave prefers real GCC for better auto‑vectorisation; MATLAB uses its default (Clang on macOS, MSVC on Windows) +3. **Sets SIMD flags** — selects instruction sets and optimisation flags +4. **Checks stamp** — if `install.m` already verified the MEX stamp, no compilation runs; otherwise `build_mex()` compiles each missing file +5. **Compiles sources** — builds all MEX files, including `mksqlite` with the bundled SQLite3 amalgamation +6. **Handles failures** — automatically retries x86_64 builds with SSE2 if AVX2 fails +7. **Copies shared files** — distributes MEX binaries (e.g., `violation_cull_mex`, `to_step_function_mex`) to `SensorThreshold/private/` for use by the Sensors library ## Verifying Installation