Event System#
anyplotlib uses a unified event system inspired by
pygfx/rendercanvas with
anyplotlib-specific extensions. Every plot class (Plot1D,
Plot2D, PlotMesh,
Plot3D, PlotBar) and every
interactive widget shares the same API.
Quick start#
import numpy as np
import anyplotlib as apl
fig, ax = apl.subplots(1, 1, figsize=(600, 400))
plot = ax.imshow(np.random.default_rng(0).standard_normal((128, 128)))
# Direct call
def on_press(event):
print(f"clicked at data ({event.xdata:.2f}, {event.ydata:.2f})")
plot.add_event_handler(on_press, "pointer_down")
# Decorator form — equivalent
@plot.add_event_handler("pointer_down")
def on_press(event):
print(f"clicked at data ({event.xdata:.2f}, {event.ydata:.2f})")
# Multiple types in one call
@plot.add_event_handler("pointer_down", "pointer_up")
def on_press_release(event):
print(event.event_type, event.button)
# Wildcard — fires for every event type
@plot.add_event_handler("*")
def log_all(event):
print(event)
# Remove by CID
cid = plot.add_event_handler(on_press, "pointer_down")
plot.remove_handler(cid)
# Remove by function reference
plot.remove_handler(on_press)
Event types#
Event type |
Trigger |
|---|---|
|
Mouse button pressed inside the panel. |
|
Mouse button released. |
|
Pointer moved (drag or hover). |
|
Pointer held still for ≥ ms milliseconds within ± delta pixels. Zero-cost when no handler is connected (timer never created). |
|
Cursor enters the panel. |
|
Cursor leaves the panel. |
|
Double-click. |
|
Scroll wheel or trackpad pinch. |
|
Key pressed while panel has focus. |
|
Key released. |
|
Wildcard — handler receives every dispatched event type. |
pointer_settled thresholds#
# Default: 300 ms dwell, 4-pixel radius
@plot.add_event_handler("pointer_settled")
def on_settle(event):
update_tooltip(event.xdata, event.ydata)
# Custom thresholds
@plot.add_event_handler("pointer_settled", ms=500, delta=8)
def on_settle_slow(event):
run_expensive_query(event.xdata, event.ydata)
The timer is activated when the first pointer_settled handler connects and
deactivated (zeroed out) when the last one disconnects, so there is no JS
overhead when the event is unused.
Event object#
Every handler receives a single Event instance.
All fields are top-level attributes — there is no nested data dict.
Universal fields (every event)#
Field |
Type |
Description |
|---|---|---|
|
|
e.g. |
|
|
The plot or widget that fired the event. |
|
|
|
|
|
Active modifier keys: |
|
|
Set to |
Pointer fields#
Present on pointer_down, pointer_up, pointer_move,
pointer_settled, pointer_enter, pointer_leave, double_click.
Field |
Type |
Description |
|---|---|---|
|
|
Canvas pixel coordinates within the panel. |
|
|
Which button was pressed or released: 0 = left, 1 = middle, 2 = right.
|
|
|
Bitmask of currently held buttons (useful on |
|
|
Data-space coordinates. Available on Plot1D, Plot2D, PlotMesh.
|
|
|
Plot3D only: |
|
|
Plot1D only. Set to the overlay line’s ID when the pointer is over a
named line; |
|
|
|
PlotBar additional fields on pointer_down#
Field |
Type |
Description |
|---|---|---|
|
|
Index of the bar that was pressed; |
|
|
Bar height at |
|
|
Category label of the pressed bar; |
|
|
Group index for grouped-bar charts; |
Wheel fields#
Field |
Type |
Description |
|---|---|---|
|
|
Pointer position at scroll time. |
|
|
Scroll deltas (positive = down/right). |
Key fields#
Field |
Type |
Description |
|---|---|---|
|
|
Key name: |
|
|
Pointer position at keypress time (useful for placing UI elements at the cursor). |
|
|
ID of the last overlay widget the user clicked, or |
Per-line filtering on Plot1D#
Lines returned by add_line() expose their own
add_event_handler. Internally this connects to the plot-level
pointer_move / pointer_down and filters by line_id, so no new
mechanism is required.
t = np.linspace(0, 2 * np.pi, 256)
fig, ax = apl.subplots(1, 1, figsize=(600, 300))
plot = ax.plot(np.sin(t))
overlay = plot.add_line(np.cos(t), color="#ff7043")
@overlay.add_event_handler("pointer_down")
def on_pick(event):
print(f"picked overlay line at xdata={event.xdata:.3f}")
Pause and hold#
Both are context managers available on every plot and widget.
Pause (suppress — events are dropped):
with plot.pause_events(): # suppress all types
update_all_panels()
with plot.pause_events("pointer_move"): # suppress specific type(s)
do_something()
Hold (buffer — events are queued and flushed on exit):
with plot.hold_events(): # buffer all types
do_something()
with plot.hold_events("pointer_settled"): # buffer specific type(s) only
do_something()
Both support nesting via a depth counter. When both are active for the same type, pause wins: events are dropped, not buffered.
Priority ordering#
Handlers fire in ascending order value (default 0). Lower values fire
first:
plot.add_event_handler(fast_handler, "pointer_move", order=-1)
plot.add_event_handler(normal_handler, "pointer_move") # order=0
plot.add_event_handler(slow_handler, "pointer_move", order=1)
Comparison with Matplotlib and pygfx#
Design goals: align naming with pygfx/rendercanvas (which inherits from DOM
conventions), fill the gaps in the old on_click/on_release/on_key
API, and add anyplotlib-specific extensions (pointer_settled,
pause_events, hold_events).
API mapping#
anyplotlib (new) |
Matplotlib equivalent |
pygfx / rendercanvas equivalent |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
(no equivalent — requires manual timer) |
(no equivalent) |
|
|
|
|
|
|
|
(detect via button_press_event + dblclick guard) |
|
|
|
|
|
|
|
|
|
|
|
(no wildcard — register for each type separately) |
|
|
(no equivalent) |
(no equivalent) |
|
(no equivalent) |
(no equivalent) |
|
|
|
Event field mapping#
anyplotlib field |
Matplotlib equivalent |
pygfx equivalent |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(absent) |
(absent) |
|
(absent — use pick_event) |
(absent) |
|
(absent — use pick_event) |
(absent) |
|
(absent) |
(absent — 3-D not a focus of rendercanvas) |
Note
Matplotlib uses a 1-based button numbering (1=left, 2=middle, 3=right). anyplotlib and pygfx both follow the DOM convention (0=left, 1=middle, 2=right).
Implementation status#
The table below tracks what is implemented, partially implemented, or planned for each event type and plot class. ✓ = fully implemented, ◑ = partial / known gap, ✗ = not yet implemented.
Event types#
Event type |
Plot1D |
Plot2D |
PlotMesh |
Plot3D |
PlotBar |
|---|---|---|---|---|---|
|
✓ |
✓ |
✓ |
✗ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✗ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✗ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
Event fields#
Field |
Plot1D |
Plot2D |
PlotMesh |
Plot3D |
PlotBar |
|---|---|---|---|---|---|
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
✓ |
✓ |
✓ |
✗ (always None) |
✗ (always None) |
|
✗ (always None) |
✗ (always None) |
✗ (always None) |
✗ (not yet impl.) |
✗ (always None) |
|
✓ |
n/a |
n/a |
n/a |
n/a |
|
✓ |
✓ |
✓ |
✓ |
✓ |
|
n/a |
n/a |
n/a |
n/a |
✓ (pointer_down only) |
|
✓ (wheel only) |
✓ (wheel only) |
✓ (wheel only) |
✓ (wheel only) |
✓ (wheel only) |
|
✓ (key events) |
✓ (key events) |
✓ (key events) |
✓ (key events) |
✓ (key events) |
Known gaps and planned work#
Gap |
Notes |
|---|---|
Plot3D |
Mousedown starts azimuth/elevation drag; a separate
|
Plot3D |
The dblclick DOM listener is not attached to the 3-D canvas. |
Plot3D |
Works correctly but always emits even if the press started outside the panel. |
|
The |
|
Fires on |
PlotBar |
The bar canvas has no |
Touch events not supported |
|
API Reference#
See also
EventFull field reference for the event dataclass.
CallbackRegistryLow-level registry:
connect,disconnect,fire,pause_events,hold_events.- Callbacks
Autogenerated API documentation for both classes.
- Examples
Gallery of interactive examples using
add_event_handler.