Skip to content
Merged
12 changes: 12 additions & 0 deletions libs/Dashboard/BarChartWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ function render(obj, parentPanel)
'Color', theme.WidgetBackground, ...
'XColor', theme.AxisColor, ...
'YColor', theme.AxisColor);
if ~isempty(obj.Title)
title(obj.hAxes, obj.Title, ...
'Color', theme.ForegroundColor, ...
'FontSize', theme.WidgetTitleFontSize);
end
obj.refresh();
end

Expand Down Expand Up @@ -83,6 +88,13 @@ function refresh(obj)
set(obj.hAxes, 'XTick', 1:numel(cats), 'XTickLabel', cats);
end
end
% Re-apply title after plot commands (bar/barh may clear via newplot)
if ~isempty(obj.Title)
theme = obj.getTheme();
title(obj.hAxes, obj.Title, ...
'Color', theme.ForegroundColor, ...
'FontSize', theme.WidgetTitleFontSize);
end
end

function t = getType(~)
Expand Down
10 changes: 10 additions & 0 deletions libs/Dashboard/ChipBarWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
function render(obj, parentPanel)
%RENDER Draw all chips in a single shared axes inside parentPanel.
obj.hPanel = parentPanel;
% Re-layout on resize so pixel-scaled fonts/geometry stay correct.
try obj.hPanel.SizeChangedFcn = @(~,~) obj.relayout_(); catch, end
theme = obj.getTheme();

nChips = numel(obj.Chips);
Expand Down Expand Up @@ -224,6 +226,14 @@ function refresh(obj)
end

methods (Access = private)
function relayout_(obj)
%RELAYOUT_ Rebuild pixel-scaled elements on panel resize.
if isempty(obj.hPanel) || ~ishandle(obj.hPanel), return; end
try delete(findobj(obj.hPanel, '-depth', 1, 'Type', 'uicontrol')); catch, end
try delete(findobj(obj.hPanel, '-depth', 1, 'Type', 'axes')); catch, end
obj.render(obj.hPanel);
end

function chipColor = resolveChipColor(~, chip, theme)
%RESOLVECHIPCOLOR Map chip struct to an [r g b] color triple.
%
Expand Down
26 changes: 26 additions & 0 deletions libs/Dashboard/EventTimelineWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ function refresh(obj)

set(obj.hAxes, 'YTick', 1:numel(labels), 'YTickLabel', labels);
set(obj.hAxes, 'YLim', [0.3, numel(labels) + 0.7]);

% Reformat time-axis ticks to HH:MM:SS / MM:SS for readability.
obj.formatTimeAxis_(obj.hAxes);
end

function t = getType(~)
Expand Down Expand Up @@ -357,5 +360,28 @@ function onXLimChanged(obj)
end
end

function formatTimeAxis_(~, ax)
%FORMATTIMEAXIS_ Replace numeric-seconds x-ticks with HH:MM:SS labels.
% No-op when range <= 300s (raw seconds readable) or ax invalid.
if isempty(ax) || ~ishandle(ax), return; end
xl = get(ax, 'XLim');
rangeSec = xl(2) - xl(1);
if rangeSec <= 300, return; end
xt = get(ax, 'XTick');
if isempty(xt), return; end
if rangeSec >= 3600
fmt = 'HH:MM:SS';
else
fmt = 'MM:SS';
end
lbl = cell(1, numel(xt));
for i = 1:numel(xt)
% xt(i) is seconds; serial-date day = seconds / 86400
lbl{i} = datestr(xt(i) / 86400, fmt);
end
set(ax, 'XTickMode', 'manual', 'XTickLabelMode', 'manual', ...
'XTickLabel', lbl);
end

end
end
31 changes: 31 additions & 0 deletions libs/Dashboard/FastSenseWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ function render(obj, parentPanel)

fp.render();

% Reformat time-axis ticks to HH:MM:SS / MM:SS for readability.
obj.formatTimeAxis_(ax);

% Apply fixed Y-axis limits if configured
if ~isempty(obj.YLimits) && numel(obj.YLimits) == 2
ylim(ax, obj.YLimits);
Expand Down Expand Up @@ -132,6 +135,7 @@ function refresh(obj)
[x, y] = obj.Tag.getXY();
obj.FastSenseObj.updateData(1, x, y);
obj.updateTimeRangeCache();
obj.formatTimeAxis_(obj.FastSenseObj.hAxes);
return;
catch
% fall through to full teardown/rebuild
Expand All @@ -153,6 +157,7 @@ function update(obj)
[x, y] = obj.Tag.getXY();
obj.FastSenseObj.updateData(1, x, y);
obj.updateTimeRangeCache();
obj.formatTimeAxis_(obj.FastSenseObj.hAxes);
return;
catch
% fall through to refresh()
Expand Down Expand Up @@ -267,6 +272,29 @@ function onXLimChanged(obj)
end

methods (Access = private)
function formatTimeAxis_(~, ax)
%FORMATTIMEAXIS_ Replace numeric-seconds x-ticks with HH:MM:SS labels.
% No-op when range <= 300s (raw seconds readable) or ax invalid.
if isempty(ax) || ~ishandle(ax), return; end
xl = get(ax, 'XLim');
rangeSec = xl(2) - xl(1);
if rangeSec <= 300, return; end
xt = get(ax, 'XTick');
if isempty(xt), return; end
if rangeSec >= 3600
fmt = 'HH:MM:SS';
else
fmt = 'MM:SS';
end
lbl = cell(1, numel(xt));
for i = 1:numel(xt)
% xt(i) is seconds; serial-date day = seconds / 86400
lbl{i} = datestr(xt(i) / 86400, fmt);
end
set(ax, 'XTickMode', 'manual', 'XTickLabelMode', 'manual', ...
'XTickLabel', lbl);
end

function updateTimeRangeCache(obj)
%UPDATETIMERANGECACHE Maintain CachedXMin/CachedXMax incrementally.
% For sorted time arrays (the common case) the last element is the
Expand Down Expand Up @@ -339,6 +367,9 @@ function rebuildForTag_(obj)

fp.render();

% Reformat time-axis ticks to HH:MM:SS / MM:SS for readability.
obj.formatTimeAxis_(ax);

if ~isempty(obj.YLimits) && numel(obj.YLimits) == 2
ylim(ax, obj.YLimits);
end
Expand Down
5 changes: 2 additions & 3 deletions libs/Dashboard/GaugeWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -501,10 +501,9 @@ function renderThermometer(obj, parentPanel)

obj.hAxes = axes('Parent', parentPanel, ...
'Units', 'normalized', ...
'Position', [0.3 0.15 0.4 0.7], ...
'Position', [0.15 0.10 0.7 0.80], ...
'Visible', 'off', ...
'XLim', [-0.5 1.5], 'YLim', [-0.3 1.3], ...
'DataAspectRatio', [1 2 1], ...
'XLim', [-0.5 1.5], 'YLim', [-0.3 1.4], ...
'HitTest', 'off');
try set(obj.hAxes, 'PickableParts', 'none'); catch , end
try disableDefaultInteractivity(obj.hAxes); catch , end
Expand Down
6 changes: 6 additions & 0 deletions libs/Dashboard/HeatmapWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ function render(obj, parentPanel)
'XColor', theme.AxisColor, ...
'YColor', theme.AxisColor);

if ~isempty(obj.Title)
title(obj.hAxes, obj.Title, ...
'Color', theme.ForegroundColor, ...
'FontSize', theme.WidgetTitleFontSize);
end

obj.refresh();
end

Expand Down
12 changes: 12 additions & 0 deletions libs/Dashboard/HistogramWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ function render(obj, parentPanel)
'Color', theme.WidgetBackground, ...
'XColor', theme.AxisColor, ...
'YColor', theme.AxisColor);
if ~isempty(obj.Title)
title(obj.hAxes, obj.Title, ...
'Color', theme.ForegroundColor, ...
'FontSize', theme.WidgetTitleFontSize);
end
obj.refresh();
end

Expand Down Expand Up @@ -70,6 +75,13 @@ function refresh(obj)
plot(obj.hAxes, xFit, yFit, 'r-', 'LineWidth', 1.5);
hold(obj.hAxes, 'off');
end
% Re-apply title after plot commands (bar/plot may clear via newplot)
if ~isempty(obj.Title)
theme = obj.getTheme();
title(obj.hAxes, obj.Title, ...
'Color', theme.ForegroundColor, ...
'FontSize', theme.WidgetTitleFontSize);
end
obj.Dirty = false;
end

Expand Down
17 changes: 16 additions & 1 deletion libs/Dashboard/IconCardWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@
end
if ~isempty(obj.Tag)
obj.Threshold = [];
obj.Sensor = [];
% NOTE: do NOT clear obj.Sensor here. Sensor is a Dependent
% alias for Tag (see DashboardWidget.set.Sensor) — setting
% it to [] wipes the Tag we just stored, causing the widget
% to render "--" forever.
end
% Mutual exclusivity: Threshold wins (per D-08)
if ~isempty(obj.Threshold) && ~isempty(obj.Sensor)
Expand All @@ -86,6 +89,8 @@
function render(obj, parentPanel)
%RENDER Create icon, value text, and label inside parentPanel.
obj.hPanel = parentPanel;
% Re-layout on resize so pixel-scaled fonts/geometry stay correct.
try obj.hPanel.SizeChangedFcn = @(~,~) obj.relayout_(); catch, end
theme = obj.getTheme();

bgColor = theme.WidgetBackground;
Expand Down Expand Up @@ -161,6 +166,8 @@ function refresh(obj)
v = obj.Tag.valueAt(now);
if ~isempty(v) && ~any(isnan(v))
obj.CurrentValue = v;
elseif isprop(obj.Tag, 'Y') && ~isempty(obj.Tag.Y)
obj.CurrentValue = obj.Tag.Y(end);
end
if isempty(obj.Units) && isprop(obj.Tag, 'Units') && ~isempty(obj.Tag.Units)
obj.Units = obj.Tag.Units;
Expand Down Expand Up @@ -333,6 +340,14 @@ function refresh(obj)
end

methods (Access = private)
function relayout_(obj)
%RELAYOUT_ Rebuild pixel-scaled elements on panel resize.
if isempty(obj.hPanel) || ~ishandle(obj.hPanel), return; end
try delete(findobj(obj.hPanel, '-depth', 1, 'Type', 'uicontrol')); catch, end
try delete(findobj(obj.hPanel, '-depth', 1, 'Type', 'axes')); catch, end
obj.render(obj.hPanel);
end

function color = resolveIconColor(obj, theme)
%RESOLVEICONCOLOR Map current state to a theme color.
switch obj.CurrentState
Expand Down
20 changes: 19 additions & 1 deletion libs/Dashboard/ImageWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ function render(obj, parentPanel)
'Position', [0.02 captionH+0.02 0.96 0.96-captionH], ...
'Visible', 'off');

if ~isempty(obj.Title)
title(obj.hAxes, obj.Title, ...
'Color', theme.ForegroundColor, ...
'FontSize', theme.WidgetTitleFontSize);
try set(get(obj.hAxes, 'Title'), 'Visible', 'on'); catch, end
end

if ~isempty(obj.Caption)
obj.hCaption = uicontrol(parentPanel, ...
'Style', 'text', ...
Expand Down Expand Up @@ -62,9 +69,20 @@ function refresh(obj)
end
if isempty(imgData), return; end

obj.hImage = image(obj.hAxes, imgData);
% For matrices (not RGB uint8), use imagesc so CData auto-scales to
% the colormap range -- image() would clip to 1..64 and render a dark block.
if ndims(imgData) == 2
obj.hImage = imagesc(obj.hAxes, imgData);
colormap(obj.hAxes, 'parula');
else
obj.hImage = image(obj.hAxes, imgData);
end
axis(obj.hAxes, 'image');
set(obj.hAxes, 'Visible', 'off');
% Keep title visible even though axes is invisible (set by render()).
if ~isempty(obj.Title)
try set(get(obj.hAxes, 'Title'), 'Visible', 'on'); catch, end
end
end

function t = getType(~)
Expand Down
10 changes: 10 additions & 0 deletions libs/Dashboard/MultiStatusWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

function render(obj, parentPanel)
obj.hPanel = parentPanel;
% Re-layout on resize so pixel-scaled fonts/geometry stay correct.
try obj.hPanel.SizeChangedFcn = @(~,~) obj.relayout_(); catch, end
theme = obj.getTheme();
obj.hAxes = axes('Parent', parentPanel, ...
'Units', 'normalized', ...
Expand Down Expand Up @@ -232,6 +234,14 @@ function refresh(obj)
end

methods (Access = private)
function relayout_(obj)
%RELAYOUT_ Rebuild pixel-scaled elements on panel resize.
if isempty(obj.hPanel) || ~ishandle(obj.hPanel), return; end
try delete(findobj(obj.hPanel, '-depth', 1, 'Type', 'uicontrol')); catch, end
try delete(findobj(obj.hPanel, '-depth', 1, 'Type', 'axes')); catch, end
obj.render(obj.hPanel);
end

function expandedItems = expandSensors_(obj)
%EXPANDSENSORS_ Expand CompositeThreshold/CompositeTag items into children + summary.
% Non-composite items pass through unchanged.
Expand Down
10 changes: 10 additions & 0 deletions libs/Dashboard/NumberWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@

function render(obj, parentPanel)
obj.hPanel = parentPanel;
% Re-layout on resize so pixel-scaled fonts/geometry stay correct.
try obj.hPanel.SizeChangedFcn = @(~,~) obj.relayout_(); catch, end
theme = obj.getTheme();

bgColor = theme.WidgetBackground;
Expand Down Expand Up @@ -198,6 +200,14 @@ function refresh(obj)
end

methods (Access = private)
function relayout_(obj)
%RELAYOUT_ Rebuild pixel-scaled elements on panel resize.
if isempty(obj.hPanel) || ~ishandle(obj.hPanel), return; end
try delete(findobj(obj.hPanel, '-depth', 1, 'Type', 'uicontrol')); catch, end
try delete(findobj(obj.hPanel, '-depth', 1, 'Type', 'axes')); catch, end
obj.render(obj.hPanel);
end

function trend = computeTrend(obj)
trend = '';
if isempty(obj.Sensor) || numel(obj.Sensor.Y) < 3
Expand Down
39 changes: 39 additions & 0 deletions libs/Dashboard/ScatterWidget.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ function render(obj, parentPanel)
'Color', theme.WidgetBackground, ...
'XColor', theme.AxisColor, ...
'YColor', theme.AxisColor);
if ~isempty(obj.Title)
title(obj.hAxes, obj.Title, ...
'Color', theme.ForegroundColor, ...
'FontSize', theme.WidgetTitleFontSize);
end
obj.refresh();
end

Expand Down Expand Up @@ -61,6 +66,16 @@ function refresh(obj)
'Marker', '.', ...
'MarkerSize', obj.MarkerSize);
end

% Auto-derive axis labels from SensorX/SensorY if present.
if ~isempty(obj.SensorX)
xl = obj.axisLabelForSensor_(obj.SensorX);
if ~isempty(xl), xlabel(obj.hAxes, xl); end
end
if ~isempty(obj.SensorY)
yl = obj.axisLabelForSensor_(obj.SensorY);
if ~isempty(yl), ylabel(obj.hAxes, yl); end
end
end

function t = getType(~)
Expand Down Expand Up @@ -107,6 +122,30 @@ function refresh(obj)
end
end

methods (Access = private)
function lbl = axisLabelForSensor_(~, s)
%AXISLABELFORSENSOR_ Build "Name (Units)" label with graceful fallbacks.
lbl = '';
if isempty(s), return; end
name = '';
if isprop(s, 'Name') && ~isempty(s.Name)
name = s.Name;
elseif isprop(s, 'Key') && ~isempty(s.Key)
name = s.Key;
end
if isempty(name), return; end
units = '';
if isprop(s, 'Units') && ~isempty(s.Units)
units = s.Units;
end
if isempty(units)
lbl = name;
else
lbl = sprintf('%s (%s)', name, units);
end
end
end

methods (Static)
function obj = fromStruct(s)
obj = ScatterWidget();
Expand Down
Loading
Loading