Skip to content

Refactor Events#14

Merged
CSSFrancis merged 43 commits into
mainfrom
refactor/project-reorganization
May 20, 2026
Merged

Refactor Events#14
CSSFrancis merged 43 commits into
mainfrom
refactor/project-reorganization

Conversation

@CSSFrancis
Copy link
Copy Markdown
Owner

This pull request updates all interactive plotting examples to use the new event handler API (add_event_handler) instead of the older callback decorators (like on_key, on_changed, on_release, etc.). It also standardizes event data access to use the event.source and event.xdata/event.ydata attributes, 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.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 18, 2026

Codecov Report

❌ Patch coverage is 95.54455% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.91%. Comparing base (ebee481) to head (189a4ea).

Files with missing lines Patch % Lines
anyplotlib/plot1d/_plotbar.py 50.00% 3 Missing ⚠️
anyplotlib/plot3d/_plot3d.py 50.00% 3 Missing ⚠️
anyplotlib/callbacks.py 98.54% 2 Missing ⚠️
anyplotlib/plot1d/_plot1d.py 95.65% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/CallbackRegistry system 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
Comment thread anyplotlib/callbacks.py
Comment on lines +38 to +66
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
Comment thread anyplotlib/callbacks.py
Comment on lines +74 to +83
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)
Comment thread anyplotlib/figure/_figure.py
Comment thread anyplotlib/figure/_figure.py Outdated
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
Comment thread anyplotlib/callbacks.py Outdated
Comment on lines +304 to +308
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"):
Copy link
Copy Markdown
Owner Author

@CSSFrancis CSSFrancis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 6 review comments addressed in commit 3a47b3b.

@CSSFrancis CSSFrancis added this to the 0.1.0 milestone May 18, 2026
@CSSFrancis CSSFrancis self-assigned this May 18, 2026
@CSSFrancis CSSFrancis linked an issue May 18, 2026 that may be closed by this pull request
@CSSFrancis CSSFrancis force-pushed the refactor/project-reorganization branch from 47388d5 to 12b6311 Compare May 18, 2026 15:34
CSSFrancis added 21 commits May 18, 2026 12:00
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.
…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_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
…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
…ar stale timers on mousemove early returns; capture modifiers at arm time
CSSFrancis added 17 commits May 18, 2026 12:06
…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.
@CSSFrancis CSSFrancis force-pushed the refactor/project-reorganization branch from 12b6311 to 6f831ee Compare May 18, 2026 17:11
@CSSFrancis CSSFrancis merged commit 0c03764 into main May 20, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Event specification

3 participants