Explorer X-Axis Shift Fix

Date: March 2, 2026 Author: Anzal K Shahul Status: COMPLETED


Overview

Fixed a bug where the Explorer tab’s X-axis would shift right (not starting at 0) when cycling through files, especially with multichannel recordings. The Y-axis was also too narrow in overlay mode when trial 0 was at resting potential but other trials contained action potentials.


Symptoms

  1. X-axis shifted right: Opening a multichannel file (e.g. 4-channel ABF) after viewing a single-channel file caused the X-axis to not start at 0. The same file displayed correctly after pressing “Reset View” manually.

  2. Y-range too narrow in overlay mode: When all trials were overlaid, only trial 0’s amplitude range was used - if trial 0 was at resting potential the Y range missed action potentials in other trials.

  3. Inconsistent view on file cycling: Rapidly cycling through files produced different view ranges than opening the same file fresh.


Root Causes

1. Stale ViewBox signals from deleteLater()’d widgets

File: src/Synaptipy/application/gui/explorer/plot_canvas.py

ExplorerPlotCanvas.rebuild_plots() creates a new GraphicsLayoutWidget and schedules the old widget for deletion via deleteLater(). However, the old ViewBoxes survive until the next event-loop iteration and can still emit sigXRangeChanged / sigYRangeChanged / sigResized signals. These signals are connected via lambdas in _configure_plot_item() to the canvas’s x_range_changed signal, which feeds into the Explorer tab’s _on_vb_x_range_changed() → slider/scrollbar update logic.

When old ViewBoxes emit during destruction, they fire signals with channel IDs that happen to match the new recording’s channel IDs, corrupting the slider/scrollbar values for the new display.

Fix: Explicitly disconnect all ViewBox signals (sigXRangeChanged, sigYRangeChanged, sigResized) in rebuild_plots() BEFORE clearing Python references to the old plot items.

3. Y range computed from trial 0 only

File: src/Synaptipy/application/gui/explorer/explorer_tab.py

_compute_channel_y_range() used only channel.get_data(trial_idx=0) to compute the Y-axis range. For recordings where trial 0 is at resting potential (e.g. −65.65 to −65.10 mV) but other trials contain action potentials (e.g. −80 to +40 mV), the Y range was far too narrow to display the full signal in overlay mode.

Fix: Sample up to 50 trials evenly spaced across all available trials and compute the global min/max from all sampled data.


Deferred Initial Reset

A generation-counter-protected _deferred_initial_reset() was added for multichannel recordings. After Qt processes initial layout geometry events (which cause sigResized callbacks that can shift ViewBox ranges), this deferred callback re-applies _reset_view() to ensure correct initial display.

The generation counter (_display_generation) prevents stale resets from previous file loads from executing if the user has already navigated to a different file. The deferred reset is only scheduled for multichannel recordings and only when no view state restoration is pending.


Files Modified

File

Change

src/Synaptipy/application/gui/explorer/plot_canvas.py

Disconnect old ViewBox signals before widget replacement

src/Synaptipy/application/gui/explorer/explorer_tab.py

Block X-link in _reset_view(), all-trial Y range, deferred initial reset


Verification

  • All 330 tests pass (zero failures)

  • flake8 clean on both modified files

  • State preservation tests (3/3) pass - deferred reset does not overwrite deliberate zoom/pan or restored view state