Problem / motivation
ScatterWidget can only plot two registered sensors against each other. Its public
config is just SensorX / SensorY / SensorColor / MarkerSize / Colormap
(ScatterWidget.m:2-8), and refresh reads strictly from obj.SensorX.getXY() /
obj.SensorY.getXY() (ScatterWidget.m:46-54).
This is a binding-model asymmetry: the two other chart-shaped data widgets both accept a
DataFcn callback as an alternative to a Tag binding —
BarChartWidget.DataFcn (BarChartWidget.m:3), consumed in refresh
(elseif ~isempty(obj.DataFcn) ... result = obj.DataFcn();, :51-52)
HistogramWidget.DataFcn (HistogramWidget.m:3), consumed in refresh
(elseif ~isempty(obj.DataFcn) ... data = obj.DataFcn();, :52-53), and round-tripped
as source.type='callback' (:126-128, :146-149)
ScatterWidget is the only one of the three with no callback path
(grep -niE "DataFcn|callback|feval" ScatterWidget.m → 0). So to scatter two computed/
transformed quantities, an analyst must first register throwaway tags just to plot them.
Proposed feature
Add an optional DataFcn callback to ScatterWidget, mirroring its sibling widgets, that
returns the XY (and optional color) data directly:
w = ScatterWidget('Title', 'Lag plot');
w.DataFcn = @() struct('x', x(1:end-1), 'y', x(2:end)); % x(t) vs x(t+1)
% optional color channel:
w.DataFcn = @() struct('x', a, 'y', b, 'c', residual);
Use cases: residual plots, lag / return-map plots, correlation of two on-the-fly computed
channels, or a pre-downsampled point cloud — none of which need to exist as registered Tags.
Rough sketch
- Lib/class:
libs/Dashboard/ScatterWidget.m (single file).
- Public API: add
DataFcn = [] (default []). Convention matches the siblings:
@() struct('x', xvec, 'y', yvec) with an optional .c color vector.
- Render: in
refresh, after the SensorX/SensorY branch, add
elseif ~isempty(obj.DataFcn) that calls r = obj.DataFcn() and reads r.x/r.y
(and r.c if present), feeding the existing rebuild block (the same
scatter(...) / line(...) path already used for the colored/uncolored cases).
Sensor binding keeps priority when both are set (matches BarChart/Histogram precedence).
- Serialize: mirror Histogram/BarChart — emit
s.source = struct('type','callback','function', func2str(obj.DataFcn)) in toStruct
when DataFcn is set and no sensors are wired; restore via str2func in fromStruct.
Value
Medium-High. Removes the "register a throwaway tag just to scatter it" friction for
exploratory XY analysis, exactly as DataFcn already does for the bar/histogram tiles.
Constraints check
- Toolbox-free: yes — anonymous handle +
feval, existing scatter/line calls.
- Backward-compatible: yes — default
DataFcn=[] is byte-identical to today; old
scatter saves have no source key, load with DataFcn=[], and render exactly as before.
- Pure MATLAB/Octave: yes. Works through the existing
DashboardWidget contract — no
base-class or layout change.
Effort estimate
S–M — one public prop + one elseif branch in refresh (reusing the existing rebuild
block) + a toStruct/fromStruct round-trip. Plus one test: a DataFcn returning
struct('x',1:10,'y',(1:10).^2) renders a 10-point cloud with no sensors wired, and the
handle round-trips through toStruct/fromStruct.
AI-proposed via /feature-scout — needs a human product decision before implementation.
Problem / motivation
ScatterWidgetcan only plot two registered sensors against each other. Its publicconfig is just
SensorX/SensorY/SensorColor/MarkerSize/Colormap(
ScatterWidget.m:2-8), andrefreshreads strictly fromobj.SensorX.getXY()/obj.SensorY.getXY()(ScatterWidget.m:46-54).This is a binding-model asymmetry: the two other chart-shaped data widgets both accept a
DataFcncallback as an alternative to a Tag binding —BarChartWidget.DataFcn(BarChartWidget.m:3), consumed inrefresh(
elseif ~isempty(obj.DataFcn) ... result = obj.DataFcn();,:51-52)HistogramWidget.DataFcn(HistogramWidget.m:3), consumed inrefresh(
elseif ~isempty(obj.DataFcn) ... data = obj.DataFcn();,:52-53), and round-trippedas
source.type='callback'(:126-128,:146-149)ScatterWidgetis the only one of the three with no callback path(
grep -niE "DataFcn|callback|feval" ScatterWidget.m→ 0). So to scatter two computed/transformed quantities, an analyst must first register throwaway tags just to plot them.
Proposed feature
Add an optional
DataFcncallback toScatterWidget, mirroring its sibling widgets, thatreturns the XY (and optional color) data directly:
Use cases: residual plots, lag / return-map plots, correlation of two on-the-fly computed
channels, or a pre-downsampled point cloud — none of which need to exist as registered Tags.
Rough sketch
libs/Dashboard/ScatterWidget.m(single file).DataFcn = [](default[]). Convention matches the siblings:@() struct('x', xvec, 'y', yvec)with an optional.ccolor vector.refresh, after theSensorX/SensorYbranch, addelseif ~isempty(obj.DataFcn)that callsr = obj.DataFcn()and readsr.x/r.y(and
r.cif present), feeding the existing rebuild block (the samescatter(...)/line(...)path already used for the colored/uncolored cases).Sensor binding keeps priority when both are set (matches BarChart/Histogram precedence).
s.source = struct('type','callback','function', func2str(obj.DataFcn))intoStructwhen
DataFcnis set and no sensors are wired; restore viastr2funcinfromStruct.Value
Medium-High. Removes the "register a throwaway tag just to scatter it" friction for
exploratory XY analysis, exactly as
DataFcnalready does for the bar/histogram tiles.Constraints check
feval, existingscatter/linecalls.DataFcn=[]is byte-identical to today; oldscatter saves have no
sourcekey, load withDataFcn=[], and render exactly as before.DashboardWidgetcontract — nobase-class or layout change.
Effort estimate
S–M — one public prop + one
elseifbranch inrefresh(reusing the existing rebuildblock) + a
toStruct/fromStructround-trip. Plus one test: aDataFcnreturningstruct('x',1:10,'y',(1:10).^2)renders a 10-point cloud with no sensors wired, and thehandle round-trips through
toStruct/fromStruct.AI-proposed via /feature-scout — needs a human product decision before implementation.