Interaction Debouncing Implementation
Author: Anzal K Shahul Email: anzal.ks@gmail.com Date: October 21, 2025
Overview
Implemented comprehensive interaction debouncing for all zoom and scroll controls in the ExplorerTab to eliminate lag and improve UI responsiveness during rapid slider/scrollbar adjustments.
Changes Made
1. Timer Initialization in __init__ (Lines 135-164)
Added debounce timers for all zoom/scroll controls:
X-axis Zoom Timer:
_x_zoom_apply_timer(50ms delay)X-axis Scroll Timer:
_x_scroll_apply_timer(50ms delay)Global Y-axis Zoom Timer:
_y_global_zoom_apply_timer(50ms delay)Global Y-axis Scroll Timer:
_y_global_scroll_apply_timer(50ms delay)Individual Y-axis Zoom Timers:
_individual_y_zoom_timers(per-channel, 50ms delay)Individual Y-axis Scroll Timers:
_individual_y_scroll_timers(per-channel, 50ms delay)
Each timer is configured as single-shot with a 50ms interval, meaning the actual action is delayed by 50ms after the last slider/scrollbar event.
2. Signal Handler Updates
Modified all zoom/scroll signal handlers to use the debouncing pattern:
X-axis Controls:
_on_x_zoom_changed: Stores value, starts timer, logs debouncing_on_x_scrollbar_changed: Stores value, starts timer (only if not updating), logs debouncing
Global Y-axis Controls:
_on_global_y_zoom_changed: Stores value, starts timer, logs debouncing_on_global_y_scrollbar_changed: Stores value, starts timer (only if not updating), logs debouncing
Individual Y-axis Controls:
_on_individual_y_zoom_changed: Stores value per channel, creates/starts timer, logs debouncing_on_individual_y_scrollbar_changed: Stores value per channel, creates/starts timer (only if not updating), logs debouncing
3. Debounced Apply Methods
Added dedicated methods that contain the actual zoom/scroll logic, called after the debounce delay:
X-axis:
_apply_debounced_x_zoom: Applies X zoom with full logic inline_apply_debounced_x_scroll: Applies X scroll with full logic inline
Global Y-axis:
_apply_debounced_y_global_zoom: Applies global Y zoom with full logic inline (not delegating to helper)_apply_debounced_y_global_scroll: Applies global Y scroll with full logic inline (not delegating to helper)
Individual Y-axis:
_get_or_create_individual_y_zoom_timer: Helper to lazily create per-channel timers_get_or_create_individual_y_scroll_timer: Helper to lazily create per-channel timers_apply_debounced_individual_y_zoom: Applies individual Y zoom with full logic inline_apply_debounced_individual_y_scroll: Applies individual Y scroll with full logic inline
4. Key Implementation Details
Inline Logic: All debounced apply methods contain the full logic inline rather than delegating to helper methods. This eliminates additional function call overhead and potential signal cascades.
State Guards: Each apply method checks
_updating_viewrangesand_updating_scrollbarsflags to prevent recursive updates during the application of changes.Lazy Timer Creation: Individual Y-axis timers are created on-demand per channel to avoid memory overhead for channels that aren’t used.
Debug Logging: All methods include detailed debug logging to track debouncing behavior and actual application of changes.
Scrollbar Blocking: Scroll handlers check
if not self._updating_scrollbarsbefore triggering debounce to prevent feedback loops.
Performance Benefits
Reduced Redraws: Instead of redrawing on every slider/scrollbar event (potentially hundreds per second), redraws only occur 50ms after the user stops adjusting.
Eliminated Cascading Updates: Inline logic prevents helper methods from triggering additional signals or updates.
Smoother User Experience: The 50ms delay is imperceptible to users but dramatically reduces computational load.
Scalability: Per-channel timers for individual controls ensure efficient resource usage even with many channels.
Verification
All tests passing (73 passed, 1 skipped):
No regressions in existing functionality
Debouncing implementation doesn’t interfere with test behavior
Signal handlers properly guard against update loops
Usage
When running the application with debug logging enabled, you’ll see messages like:
[_on_x_zoom_changed] Debouncing X zoom: 150
[_apply_debounced_x_zoom] Applying X zoom: 150
This confirms the debouncing is working: the first message appears immediately when the slider moves, the second appears 50ms after the last movement.
Future Enhancements
If additional lag is observed:
Consider increasing debounce interval to 75-100ms for slower systems
Implement adaptive debouncing based on render time
Add downsampling hints to PyQtGraph during active zooming
Consider using QTimer.singleShot with lambda for even lighter-weight timers