Skip to content

Commit c4906df

Browse files
HanSur94claude
andauthored
feat(fastsense): hover crosshair + datatip with colored bullets (999.2) (#112)
* feat(260508-d8y-01): add HoverCrosshair class with chained motion handler - New libs/FastSense/HoverCrosshair.m: handle class managing per-axes hover crosshair line + multi-line datatip lifecycle - Chains existing WindowButtonMotionFcn so toolbar crosshair and NavigatorOverlay drag continue to work - Pixel-bounds hit-test (skips hidden tabs/panels), ~40 Hz throttle, re-entrancy guard, em-dash for out-of-range / NaN y - Theme-consistent colors via FastSense.Theme; safe fallbacks when theme is empty or partial - Self-cleanup via ObjectBeingDestroyed listeners on figure + axes; delete() restores prior callback unconditionally - tests/test_hover_crosshair.m: Octave-style smoke + chain + cleanup tests, gracefully skip on headless or pre-Task-2 builds Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(260508-d8y-02): wire HoverCrosshair into FastSense lifecycle - Add HoverCrosshair public property (default true) for hover crosshair + datatip - Add HoverCrosshair_ private property holding runtime instance - Constructor accepts 'HoverCrosshair' option via parseOpts - render() instantiates HoverCrosshair(obj) when enabled (try/catch for resilience) - delete() tears down HoverCrosshair_ first so chained motion handler is restored before figure teardown - Backward compatible: opt-out via fp.HoverCrosshair=false; serialization untouched Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(quick-260508-d8y): record FastSense hover crosshair + datatip in STATE.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(hover-crosshair): coerce CurrentPoint to pixels regardless of fig Units DashboardEngine creates classic figures with Units='normalized', so get(hFigure,'CurrentPoint') returned normalized [0..1] coords while getpixelposition() always returns pixels. The hit test in onFigureMove_ compared mismatched coordinate systems and always fell through to onLeave(), so hover never showed inside dashboard widgets (it worked for standalone FastSense figures because those default to Units='pixels'). Switch the figure to Units='pixels' just long enough to read CurrentPoint, then restore. No layout impact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(hover-crosshair): colored bullet per row in datatip Each per-line row now starts with a TeX-rendered \bullet in that line's actual rendered color (read from lineRec.hLine 'Color' with fall-back to Options.Color), followed by the existing "DisplayName: yStr". Multi-line plots become readable at a glance: the bullet matches the line, the name matches the bullet. - Tip box now uses Interpreter='tex' (set both at create-time and on every refresh) so \color[rgb]{...}\bullet directives render. - New static helpers HoverCrosshair.resolveLineColor_ and HoverCrosshair.escapeTeX_ keep TeX-special chars in DisplayNames from breaking layout. - Header row (formatted x value) is unchanged — no bullet. HoverCrosshair stays default-on with opt-out via 'HoverCrosshair', false in the FastSense constructor (already shipped in 289216a). All 10 targeted tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(hover-crosshair): add multi-line dashboard widget test helper MultiLineFastSenseWidget hosts multiple FastSense lines on a single DashboardEngine widget panel — used to manually verify the colored- bullet hover datatip end-to-end (multi-line, in-dashboard, themed). Not a shipped widget; lives under tests/helpers/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e8b5a25 commit c4906df

5 files changed

Lines changed: 870 additions & 3 deletions

File tree

.planning/STATE.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ gsd_state_version: 1.0
33
milestone: v3.0
44
milestone_name: FastSense Companion
55
status: shipped
6-
last_updated: "2026-05-08T07:35:00.000Z"
7-
last_activity: 2026-05-08 -- Completed quick task 260508-d7k: fix companion app dark mode — uilistbox + 7 prophylactic widget classes added to theme walker
6+
last_updated: "2026-05-08T08:00:00.000Z"
7+
last_activity: 2026-05-08 -- Completed quick task 260508-d8y: FastSense hover crosshair + datatip
88
progress:
99
total_phases: 6
1010
completed_phases: 6
@@ -19,7 +19,7 @@ shipped_at: 2026-04-30
1919

2020
Milestone: v3.0 FastSense Companion — SHIPPED 2026-04-30
2121
Status: Awaiting next milestone (run `/gsd:new-milestone` to scope v3.x or v4.0)
22-
Last activity: 2026-05-08 -- Completed quick task 260508-d7k: fix companion app dark mode — uilistbox + 7 prophylactic widget classes added to theme walker
22+
Last activity: 2026-05-08 -- Completed quick task 260508-d8y: FastSense hover crosshair + datatip
2323

2424
### Quick Tasks Completed
2525

@@ -32,6 +32,7 @@ Last activity: 2026-05-08 -- Completed quick task 260508-d7k: fix companion app
3232
| 260508-bju | Lock down WebBridge CORS to localhost with env-var override | 2026-05-08 | 518b778 | Verified | [260508-bju-lock-down-webbridge-cors-to-localhost-on](./quick/260508-bju-lock-down-webbridge-cors-to-localhost-on/) |
3333
| 260508-bxh | Gate WebSocket /ws endpoint with same origin policy as HTTP CORS | 2026-05-08 | e1aeebc || [260508-bxh-gate-websocket-ws-endpoint-with-same-ori](./quick/260508-bxh-gate-websocket-ws-endpoint-with-same-ori/) |
3434
| 260508-d7k | Fix companion app dark mode — add uilistbox + 7 widget classes to theme walker | 2026-05-08 | 4472cc2 | Verified | [260508-d7k-fix-companion-app-dark-mode-switching-th](./quick/260508-d7k-fix-companion-app-dark-mode-switching-th/) |
35+
| 260508-d8y | FastSense hover crosshair + datatip | 2026-05-08 | 0221795 || [260508-d8y-fastsense-hover-crosshair-datatip](./quick/260508-d8y-fastsense-hover-crosshair-datatip/) |
3536

3637
## Progress Bar
3738

libs/FastSense/FastSense.m

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
ShowThresholdLabels = false % show inline name labels on threshold lines
8989
ShowEventMarkers = true % toggle event round-marker overlay (EVENT-07)
9090
EventStore = [] % EventStore handle for event overlay queries
91+
HoverCrosshair = true % enable hover crosshair + multi-line datatip (set false to disable; see HoverCrosshair.m)
9192
end
9293

9394
% ====================== INTERNAL DATA STORAGE ========================
@@ -153,6 +154,7 @@
153154
properties (SetAccess = private)
154155
hEventDetails_ = [] % popup figure handle (empty when no popup open)
155156
EventContextMenu_ = [] % cached uicontextmenu (lazy; one per FastSense)
157+
HoverCrosshair_ = [] % HoverCrosshair instance (created in render() when HoverCrosshair=true)
156158
end
157159

158160
% ===================== PERFORMANCE TUNING ============================
@@ -216,6 +218,7 @@
216218
defaults.YScale = cfg.YScale;
217219
defaults.StorageMode = cfg.StorageMode;
218220
defaults.MemoryLimit = cfg.MemoryLimit;
221+
defaults.HoverCrosshair = true;
219222
[opts, ~] = parseOpts(defaults, varargin);
220223

221224
obj.ParentAxes = opts.Parent;
@@ -230,6 +233,7 @@
230233
obj.YScale = opts.YScale;
231234
obj.StorageMode = opts.StorageMode;
232235
obj.MemoryLimit = opts.MemoryLimit;
236+
obj.HoverCrosshair = logical(opts.HoverCrosshair);
233237
obj.Theme = resolveTheme(opts.Theme, cfg.Theme);
234238
end
235239

@@ -1571,6 +1575,19 @@ function render(obj, progressBar)
15711575
end
15721576
drawnow;
15731577
end
1578+
1579+
% Attach hover crosshair (default on; opt-out via constructor or property).
1580+
% try/catch keeps render() resilient on platforms where mouse handlers misbehave.
1581+
if obj.HoverCrosshair && isempty(obj.HoverCrosshair_)
1582+
try
1583+
obj.HoverCrosshair_ = HoverCrosshair(obj);
1584+
catch ME
1585+
if obj.Verbose
1586+
fprintf('[FastSense] HoverCrosshair init failed: %s\n', ME.message);
1587+
end
1588+
obj.HoverCrosshair_ = [];
1589+
end
1590+
end
15741591
end
15751592

15761593
function result = lookupMetadata(obj, lineIdx, xValue)
@@ -1898,6 +1915,12 @@ function delete(obj)
18981915
% timer callbacks referencing deleted objects.
18991916
%
19001917
% See also stopLive, stopRefineTimer.
1918+
% Tear down HoverCrosshair FIRST so its chained motion handler
1919+
% is restored on the figure before any other teardown runs.
1920+
if ~isempty(obj.HoverCrosshair_) && isvalid(obj.HoverCrosshair_)
1921+
try delete(obj.HoverCrosshair_); catch; end
1922+
end
1923+
obj.HoverCrosshair_ = [];
19011924
obj.stopRefineTimer();
19021925
try obj.stopLive(); catch; end
19031926
% Clean up disk-backed DataStores

0 commit comments

Comments
 (0)