Problem / motivation
The big-number NumberWidget is the canonical dashboard KPI tile — it shows a single live scalar bound to a Tag (e.g. "Pressure: 152 psi"). But it renders that value in a fixed foreground color regardless of whether the reading is nominal or in alarm. A value that crosses its MonitorTag limit looks identical to a healthy one, so the most-glanced-at widget on a dashboard cannot answer the core sensor-analysis question: is this reading in alarm?
This is a clean feature asymmetry. Every other value-display widget already colors its value by the bound Tag's thresholds:
| Widget |
colors value by Sensor.Thresholds? |
| GaugeWidget |
✅ (getValueColor, GaugeWidget.m:252; threshold walk :290) |
| StatusWidget |
✅ (threshold walk StatusWidget.m:391-392) |
| MultiStatusWidget |
✅ |
| IconCardWidget |
✅ |
| NumberWidget |
❌ |
| SparklineCardWidget |
❌ |
grep -ciE "threshold|zone" confirms the gap: Status 43, MultiStatus 41, IconCard 41, Gauge 36, vs NumberWidget 3 (all in the trend-arrow slope test computeTrend, NumberWidget.m:222-225) and SparklineCard 1 (a layout doc-comment). Neither actually colors its value.
Proposed feature
Add an opt-in ColorByThreshold mode to NumberWidget. When enabled and the bound Tag carries thresholds, color the value text by the active threshold zone — reusing the same Sensor.Thresholds evaluation Gauge/Status already perform. Default off, so every existing dashboard renders byte-identically.
Rough sketch
- Lib/class:
libs/Dashboard/NumberWidget.m (primary). SparklineCardWidget is a natural sibling follow-up, kept out of scope here.
- Public API: new property
ColorByThreshold = false (logical). Optionally allow an explicit override color map later, but the MVP just reads the Tag.
- Where: in
refresh() after obj.CurrentValue is computed (NumberWidget.m:110-148), if ColorByThreshold && ~isempty(obj.Sensor) && ~isempty(obj.Sensor.Thresholds), resolve the zone color for CurrentValue and set(obj.hValueText, 'ForegroundColor', zoneColor); otherwise keep the current fgColor path (render, :81).
- Reuse: factor the value→zone-color logic so Gauge/Status/Number share one helper rather than three copies (the threshold-walk already exists in
GaugeWidget.getValueColor / StatusWidget).
- Serialization: add
s.colorByThreshold to toStruct/fromStruct (:188, :234), omitting it when default so legacy serialized dashboards stay diff-invisible.
Value
High. Alarm/threshold ergonomics on the most-used KPI tile — a pressure/temperature reading that turns red (or amber) the moment it crosses its MonitorTag limit is exactly what a sensor-analysis engineer expects from a number tile, and today it's impossible without dropping to a Gauge/Status widget.
Constraints check
- Toolbox-free: ✅ — only
set(..., 'ForegroundColor', ...) and the existing threshold evaluation; no toolboxes.
- Backward-compatible: ✅ —
ColorByThreshold defaults false; omitted from toStruct when default, so existing scripts and serialized dashboards are unaffected.
- Pure MATLAB/Octave: ✅.
- Widget contract: ✅ — works entirely through the existing
DashboardWidget/Sensor (Tag) binding; no engine or contract change.
Effort estimate
S–M — single file plus a small shared threshold→color helper (the threshold-walk pattern already exists in Gauge/Status). No new dependencies, no public-surface migration.
AI-proposed via /feature-scout — needs a human product decision before implementation.
Problem / motivation
The big-number
NumberWidgetis the canonical dashboard KPI tile — it shows a single live scalar bound to a Tag (e.g. "Pressure: 152 psi"). But it renders that value in a fixed foreground color regardless of whether the reading is nominal or in alarm. A value that crosses itsMonitorTaglimit looks identical to a healthy one, so the most-glanced-at widget on a dashboard cannot answer the core sensor-analysis question: is this reading in alarm?This is a clean feature asymmetry. Every other value-display widget already colors its value by the bound Tag's thresholds:
Sensor.Thresholds?getValueColor,GaugeWidget.m:252; threshold walk:290)StatusWidget.m:391-392)grep -ciE "threshold|zone"confirms the gap: Status 43, MultiStatus 41, IconCard 41, Gauge 36, vs NumberWidget 3 (all in the trend-arrow slope testcomputeTrend,NumberWidget.m:222-225) and SparklineCard 1 (a layout doc-comment). Neither actually colors its value.Proposed feature
Add an opt-in
ColorByThresholdmode toNumberWidget. When enabled and the bound Tag carries thresholds, color the value text by the active threshold zone — reusing the sameSensor.Thresholdsevaluation Gauge/Status already perform. Default off, so every existing dashboard renders byte-identically.Rough sketch
libs/Dashboard/NumberWidget.m(primary).SparklineCardWidgetis a natural sibling follow-up, kept out of scope here.ColorByThreshold = false(logical). Optionally allow an explicit override color map later, but the MVP just reads the Tag.refresh()afterobj.CurrentValueis computed (NumberWidget.m:110-148), ifColorByThreshold && ~isempty(obj.Sensor) && ~isempty(obj.Sensor.Thresholds), resolve the zone color forCurrentValueandset(obj.hValueText, 'ForegroundColor', zoneColor); otherwise keep the currentfgColorpath (render,:81).GaugeWidget.getValueColor/StatusWidget).s.colorByThresholdtotoStruct/fromStruct(:188,:234), omitting it when default so legacy serialized dashboards stay diff-invisible.Value
High. Alarm/threshold ergonomics on the most-used KPI tile — a pressure/temperature reading that turns red (or amber) the moment it crosses its
MonitorTaglimit is exactly what a sensor-analysis engineer expects from a number tile, and today it's impossible without dropping to a Gauge/Status widget.Constraints check
set(..., 'ForegroundColor', ...)and the existing threshold evaluation; no toolboxes.ColorByThresholddefaultsfalse; omitted fromtoStructwhen default, so existing scripts and serialized dashboards are unaffected.DashboardWidget/Sensor(Tag) binding; no engine or contract change.Effort estimate
S–M — single file plus a small shared threshold→color helper (the threshold-walk pattern already exists in Gauge/Status). No new dependencies, no public-surface migration.
AI-proposed via /feature-scout — needs a human product decision before implementation.