Refactor Events#14
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #14 +/- ##
==========================================
+ Coverage 78.31% 81.91% +3.60%
==========================================
Files 29 29
Lines 2389 2356 -33
==========================================
+ Hits 1871 1930 +59
+ Misses 518 426 -92 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR migrates anyplotlib’s interactive examples, plot/widget APIs, and event dispatch pipeline from the legacy decorator callbacks (on_changed, on_release, on_click, on_key, etc.) to a unified event-handler API (add_event_handler) with standardized event names (pointer_*, key_*, wheel, etc.), and expands Playwright coverage for the new event model.
Changes:
- Replaces legacy callback decorators with
add_event_handler(...)across interactive examples and core plot/widget classes. - Redesigns the Python
Event/CallbackRegistrysystem and updates JS (figure_esm.js) to emit the new event types (including pointer/key enter/leave, wheel, and pointer-settled dwell). - Updates/extends tests to validate the new event pipeline (unit tests + Playwright integration tests).
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| Examples/PlotTypes/plot_image2d.py | Updates widget settle handlers to pointer_up and uses event.source for widget state. |
| Examples/Interactive/plot_segment_by_contrast.py | Migrates click + key bindings to pointer_down/key_down and uses event.xdata/event.ydata. |
| Examples/Interactive/plot_point_widget.py | Migrates drag/release callbacks to pointer_move/pointer_up and reads widget state from event.source. |
| Examples/Interactive/plot_key_bindings.py | Migrates key handlers to key_down and uses event.xdata/event.ydata. |
| Examples/Interactive/plot_interactive_fitting.py | Migrates widget and line click interactions to new handler API and event.source access. |
| Examples/Interactive/plot_interactive_fft.py | Migrates ROI widget drag/release to pointer_move/pointer_up. |
| Examples/Interactive/plot_3d_spectral_viewer.py | Migrates widget motion/release and key toggles to new handler API. |
| anyplotlib/widgets/_base.py | Makes widgets event-capable via _EventMixin, changes internal callback firing, and refactors JS→Python sync. |
| anyplotlib/tests/test_plot1d/test_plotbar.py | Updates PlotBar interaction tests to pointer_down/pointer_move and new handler API. |
| anyplotlib/tests/test_layouts/test_interaction.py | Updates Playwright interaction assertions to pointer_move/pointer_up event types. |
| anyplotlib/tests/test_layouts/test_inset.py | Renames inset event type to inset_state_change. |
| anyplotlib/tests/test_interactive/test_widgets.py | Updates widget tests for _EventMixin and new pointer event types. |
| anyplotlib/tests/test_interactive/test_event_settled.py | Adds unit + Playwright coverage for pointer_settled dwell behavior. |
| anyplotlib/tests/test_interactive/test_event_plots.py | Adds Playwright tests validating emitted event payloads/types for 2D/3D panels. |
| anyplotlib/tests/test_interactive/test_event_pause_hold.py | Adds tests for pause_events/hold_events semantics (Python dispatch + Playwright smoke). |
| anyplotlib/tests/test_interactive/test_callbacks.py | Replaces old callback API tests with tests for the redesigned Event, CallbackRegistry, and _EventMixin. |
| anyplotlib/tests/test_interactive/_event_test_utils.py | Adds shared helpers for event-related Playwright tests. |
| anyplotlib/plot3d/_plot3d.py | Migrates Plot3D to _EventMixin and adds pointer-settled config push fields. |
| anyplotlib/plot2d/_plot2d.py | Migrates Plot2D to _EventMixin and adds pointer-settled config push fields. |
| anyplotlib/plot1d/_plotbar.py | Migrates PlotBar to _EventMixin and adds pointer-settled config push fields. |
| anyplotlib/plot1d/_plot1d.py | Migrates Plot1D to _EventMixin, adds pointer-settled config fields, and refactors Line1D to the new handler model. |
| anyplotlib/figure/_figure.py | Refactors event dispatch to build the new flat Event from JS payloads and handle inset_state_change. |
| anyplotlib/figure_esm.js | Renames emitted events to new pointer_*/key_* types and adds dwell/enter/leave/wheel/dblclick emission. |
| anyplotlib/callbacks.py | Implements the redesigned Event, CallbackRegistry (priority/wildcard/pause/hold), and _EventMixin. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """Press Backspace/Delete — remove the last widget that was clicked.""" | ||
| if event.key not in ('Backspace', 'Delete'): | ||
| return | ||
| wid = event.last_widget_id |
| Pointer fields (pointer_* and double_click events): | ||
| x, y — pixel coordinates within the panel | ||
| button — 0=left 1=middle 2=right; None on move/enter/leave/settled | ||
| buttons — bitmask of currently held buttons | ||
| xdata, ydata — data-space coordinates (None for Plot3D) | ||
| ray — Plot3D only: {"origin": [...], "direction": [...]} | ||
| line_id — Plot1D only: set when pointer is over a line | ||
| dwell_ms — pointer_settled only: actual dwell time | ||
|
|
||
| For ``on_line_hover`` and ``on_line_click`` events the data dict | ||
| contains: | ||
| PlotBar extra fields (pointer_down only): | ||
| bar_index, value, x_label, group_index | ||
|
|
||
| * ``line_id`` – ``None`` for the primary line, or the 8-char ID | ||
| string assigned by :meth:`Plot1D.add_line`. | ||
| * ``x`` – data-space x coordinate of the nearest point on the line. | ||
| * ``y`` – data-space y coordinate of the nearest point on the line. | ||
| Wheel fields: | ||
| dx, dy — scroll deltas | ||
|
|
||
| Key fields: | ||
| key — key name e.g. "q", "Enter", "ArrowLeft" | ||
|
|
||
| Propagation: | ||
| stop_propagation — set True inside a handler to halt remaining handlers | ||
| """ | ||
| event_type: str | ||
| source: Any | ||
| data: dict = field(default_factory=dict) | ||
|
|
||
| def __getattr__(self, key: str) -> Any: | ||
| try: | ||
| return self.data[key] | ||
| except KeyError: | ||
| raise AttributeError( | ||
| f"Event has no attribute {key!r}. " | ||
| f"Available data keys: {list(self.data)}" | ||
| ) from None | ||
| source: Any = None | ||
| time_stamp: float = field(default_factory=time.perf_counter) | ||
| modifiers: list[str] = field(default_factory=list) | ||
| # Pointer | ||
| x: int | None = None | ||
| y: int | None = None | ||
| button: int | None = None |
| bar_index: int | None = None | ||
| value: float | None = None | ||
| x_label: str | None = None | ||
| group_index: int | None = None | ||
| # Wheel | ||
| dx: float | None = None | ||
| dy: float | None = None | ||
| # Key | ||
| key: str | None = None | ||
| # Propagation (not repr'd) |
| from anyplotlib.axes._inset_axes import _plot_kind | ||
| from anyplotlib.figure._gridspec import SubplotSpec | ||
| from anyplotlib.callbacks import Event | ||
| from anyplotlib.callbacks import CallbackRegistry, Event |
| if isinstance(cid_or_fn, int): | ||
| self.callbacks.disconnect(cid_or_fn) | ||
| else: | ||
| self.callbacks.disconnect_fn(cid_or_fn, *types) | ||
| if not self.callbacks._handlers.get("pointer_settled"): |
CSSFrancis
left a comment
There was a problem hiding this comment.
All 6 review comments addressed in commit 3a47b3b.
47388d5 to
12b6311
Compare
…per-panel ms/delta from state
Audited the existing event system against pygfx/rendercanvas conventions, identified naming inconsistencies and gaps, and designed a complete replacement aligned with pygfx naming (pointer_down/up/move/settled, key_down/key_up, etc.) with anyplotlib-specific extensions (pointer_settled with ms/delta params, pause_events/hold_events context managers).
…level attrs Replaces the old Event(event_type, source, data: dict) + __getattr__ proxy with a flat dataclass where every payload field (x, y, xdata, ydata, key, modifiers, etc.) is a typed top-level attribute with sensible defaults. Exports VALID_EVENT_TYPES frozenset and keeps CallbackRegistry as a minimal placeholder ahead of the full Task 2 rewrite.
…t_fn, stop_propagation
…gistry Implement two context manager methods in CallbackRegistry to control event dispatching: pause_events() suppresses events while active, hold_events() buffers events and flushes them on exit. Pause takes precedence over hold for the same event type.
… on_* decorators and registered_keys
…/on_click/disconnect; update tests - Widget base class now inherits _EventMixin for add_event_handler/remove_handler API - Removed on_changed, on_release, on_click decorator methods and disconnect from Widget - Fixed _update_from_js envelope: removed x, y, xdata, ydata so widget position fields named x/y are properly updated from JS events - Added on_line_click, on_line_hover to VALID_EVENT_TYPES in callbacks.py to support Line1D event routing from the JS dispatcher - Updated test_widgets.py: all tests now use add_event_handler/remove_handler, new pointer_* event types, and read widget state from widget._data not event.data - Updated test_plotbar.py: callback tests use add_event_handler/remove_handler and pointer_down/pointer_move event types with flat Event fields
…ointer_down in line tests
…l, key_up; rename event types and fields; remove registered_keys filtering - Rename event type strings: on_changed→pointer_move, on_release→pointer_up, on_click/on_line_click→pointer_down, on_line_hover→pointer_move, on_key→key_down, on_inset_state_change→inset_state_change - Rename payload fields: phys_x→xdata, phys_y→ydata, mouse_x→x, mouse_y→y - Add _modifiers() and _pointerFields() helpers; spread into all pointer events - Add new listeners: pointer_enter/leave (mouseenter/mouseleave), double_click (dblclick), wheel, key_up for all plot types (1d, 2d, 3d, bar) - Remove registered_keys guard from all keydown handlers; all keys now forwarded unconditionally to Python while built-in shortcuts still run normally - Update test_interaction.py assertions to use new event type names
…defined, clean stale comments
…per-panel ms/delta from state
…ar stale timers on mousemove early returns; capture modifiers at arm time
…ause/hold integration
…t; add delta=0 assertion
…ositive assertion
…xamples if needed
All Example files using on_click/on_changed/on_release/on_key/on_hover are
updated to use add_event_handler("pointer_down") etc. Regression tests added
to test_callbacks.py asserting these old methods no longer exist on plots,
widgets, or the Event dataclass.
…on test; add Plot3D/Bar coverage
…t button: e.button
…es, remove_handler guard, unused import
…re and implementation-status table
…_function instead of fixed sleep
12b6311 to
6f831ee
Compare
…and ROI inspector scripts
…rspectral datasets
…rspectral datasets
This pull request updates all interactive plotting examples to use the new event handler API (
add_event_handler) instead of the older callback decorators (likeon_key,on_changed,on_release, etc.). It also standardizes event data access to use theevent.sourceandevent.xdata/event.ydataattributes, and updates documentation and code comments to match these changes. This modernizes the codebase, improves consistency, and clarifies the event model for users.Based on https://pygfx.org/renderview with some additional features added.