@@ -495,25 +495,42 @@ function updateSparkTicks_(obj, tv, y, ax)
495495
496496 function [tv , y ] = windowSparkData_(obj , tv , y )
497497 % WINDOWSPARKDATA_ Filter (X,Y) to the trailing SparkWindowSec_ horizon.
498- % Assumes X is in seconds (datenum-day fallback handled below by
499- % converting day-fractions to seconds when the span looks tiny) .
498+ % Also coerces non-numeric Y (e.g. StateTag cellstr) to numeric
499+ % state-indices so plot() can render it .
500500 if isempty(tv ); return ; end
501+ % Coerce Y first so the windowing code below is uniform.
502+ y = obj .coerceSparkY_(y );
501503 xMax = tv(end );
502504 xMin = xMax - obj .SparkWindowSec_;
503- % Detect MATLAB datenum (units = days) — span < 0.1 means seconds-as-time
504- % won't work; convert window to days for the comparison.
505- if (xMax - tv(1 )) < 1 && (xMax > 7e5 ) % datenum heuristic: epoch ~7e5
505+ % datenum (days) heuristic: epoch ~7e5, tiny span.
506+ if (xMax - tv(1 )) < 1 && (xMax > 7e5 )
506507 xMin = xMax - (obj .SparkWindowSec_ / 86400 );
507508 end
508509 mask = tv >= xMin ;
509510 tv = tv(mask ); y = y(mask );
510- % Cap to ~500 points for plot perf without distorting shape.
511511 if numel(tv ) > 500
512512 idx = round(linspace(1 , numel(tv ), 500 ));
513513 tv = tv(idx ); y = y(idx );
514514 end
515515 end
516516
517+ function y = coerceSparkY_(~, y )
518+ % COERCESPARKY_ Convert StateTag cellstr / logical Y into plottable numerics.
519+ % StateTag.Y can be a cellstr of state names; plot() rejects that.
520+ % Map each unique label to its first-seen index so the sparkline
521+ % shows state transitions as a step-shaped numeric line.
522+ try
523+ if iscell(y )
524+ [~ , ~ , idx ] = unique(y , ' stable' );
525+ y = double(reshape(idx , 1 , []));
526+ elseif islogical(y )
527+ y = double(y );
528+ end
529+ catch
530+ y = [];
531+ end
532+ end
533+
517534 function updateRangeLabel_(obj , tv )
518535 % UPDATERANGELABEL_ Refresh the "Range: last X (max. 30 min)" label.
519536 if isempty(obj .hRangeLbl_) || ~isvalid(obj .hRangeLbl_); return ; end
@@ -644,15 +661,22 @@ function fitSparkAxes_(obj, ax)
644661
645662 function renderNoData_(obj , msgText )
646663 % RENDERNODATA_ Replace sparkline area with a centered placeholder label.
647- if ~isempty(obj .hSparkAxes_) && isvalid(obj .hSparkAxes_)
648- delete(obj .hSparkAxes_); obj.hSparkAxes_ = [];
649- end
664+ % Wrap in a 1×1 uigridlayout so the uilabel is properly centered
665+ % (uilabel has no Position; without a layout it pins to (0,0) and
666+ % can be invisible inside the panel).
667+ if isempty(obj .hSparkPanel_) || ~isvalid(obj .hSparkPanel_); return ; end
668+ % Clear any prior axes/children so we don't stack placeholders.
669+ try ; delete(obj .hSparkPanel_.Children); catch ; end
670+ obj.hSparkAxes_ = []; obj.hSparkLine_ = [];
650671 t = obj .Theme_;
651- lb = uilabel(obj .hSparkPanel_); lb.Text = msgText ; lb.FontSize = 11 ;
652- lb.FontColor = t .PlaceholderTextColor; lb.HorizontalAlignment = ' center' ;
672+ g = uigridlayout(obj .hSparkPanel_, [1 1 ]);
673+ g.RowHeight = {' 1x' }; g.ColumnWidth = {' 1x' };
674+ g.Padding = [0 0 0 0 ]; g.BackgroundColor = t .WidgetBackground;
675+ lb = uilabel(g );
676+ lb.Text = msgText ; lb.FontSize = 11 ;
677+ lb.FontColor = t .PlaceholderTextColor;
678+ lb.HorizontalAlignment = ' center' ;
653679 lb.VerticalAlignment = ' center' ;
654- % Note: uilabel has no Units/Position — those are uicontrol
655- % properties. Default placement inside the parent uipanel.
656680 end
657681
658682 function onOpenDetail_(obj , tag )
@@ -1028,11 +1052,16 @@ function renderMultiSparkline_(obj, idx, tag)
10281052 end
10291053
10301054 function renderMultiNoData_(obj , idx , msgText )
1031- % RENDERMULTINODATA_ Show a placeholder label when a tag has no data.
1055+ % RENDERMULTINODATA_ Show a centered placeholder when a card has no data.
10321056 sp = obj.hMultiSparkPanels_{idx };
10331057 if isempty(sp ) || ~isvalid(sp ); return ; end
1058+ try ; delete(sp .Children); catch ; end
10341059 t = obj .Theme_;
1035- lb = uilabel(sp ); lb.Text = msgText ; lb.FontSize = 10 ;
1060+ g = uigridlayout(sp , [1 1 ]);
1061+ g.RowHeight = {' 1x' }; g.ColumnWidth = {' 1x' };
1062+ g.Padding = [0 0 0 0 ]; g.BackgroundColor = t .WidgetBackground;
1063+ lb = uilabel(g );
1064+ lb.Text = msgText ; lb.FontSize = 10 ;
10361065 lb.FontColor = t .PlaceholderTextColor;
10371066 lb.HorizontalAlignment = ' center' ;
10381067 lb.VerticalAlignment = ' center' ;
0 commit comments