How to determine which events are connected in Matplotlib?

534 Views Asked by At

Is there a function or method in Matplotlib that will tell you which events have been connected, and perhaps what code is being called by that listener? I've looked all over, no joy. I'm looking preferably for a generic solution, not one that is backend specific, but I will take what I can get.

1

There are 1 best solutions below

0
On

In these situations, don't be afraid of the matplotlib codebase - it is quite reasonably pythonic and legible for the most part (with perhaps one or two skeletons here and there).

I'm going to talk you through the steps I'm going to take to understand what is going on and what you can get access to.

First off, start at a tagged version of matplotlib on GitHub - it will make all the links consistent (and not rot as code moves around). https://github.com/matplotlib/matplotlib/tree/v3.0.2

Our entry point is that we attach events through the mpl_connect method. A quick search for this (using "def mpl_connect", including the quotes) in the matplotlib codebase turns up https://github.com/matplotlib/matplotlib/blob/v3.0.2/lib/matplotlib/backend_bases.py#L2134-L2180.

self.callbacks.connect(s, func)

So now we need to figure out what self.callbacks actually is on this object. I did a ctrl+f to find the text self.callbacks = in this file. https://github.com/matplotlib/matplotlib/blob/v3.0.2/lib/matplotlib/backend_bases.py#L1621

    # a dictionary from event name to a dictionary that maps cid->func
    self.callbacks = cbook.CallbackRegistry()

We are making progress now, and getting a little deeper each time :) Following the imports logically, we now need to find out what matplotlib.cbook.CallbackRegistry looks like. https://github.com/matplotlib/matplotlib/blob/v3.0.2/lib/matplotlib/cbook/init.py#L88.

Specifically, we were calling the CallbackRegistry.connect method: https://github.com/matplotlib/matplotlib/blob/v3.0.2/lib/matplotlib/cbook/init.py#L162-L177. Implementation at time of writing:

def connect(self, s, func):
    """Register *func* to be called when signal *s* is generated.
    """
    self._func_cid_map.setdefault(s, {})
    try:
        proxy = WeakMethod(func, self._remove_proxy)
    except TypeError:
        proxy = _StrongRef(func)
    if proxy in self._func_cid_map[s]:
        return self._func_cid_map[s][proxy]

    cid = next(self._cid_gen)
    self._func_cid_map[s][proxy] = cid
    self.callbacks.setdefault(s, {})
    self.callbacks[s][cid] = proxy
    return cid

Rather than trying to read all of that, I'm looking for public data structures (those that don't start with a _) to see if there is anything I can query. It is starting to look like CallbackRegistry.callbacks is a dictionary that maps event names to some form of collection containing those event functions.

Indeed, this is supported by trying it out:

In [6]: fig.canvas.callbacks.callbacks                                                                                                                               
Out[6]: 
{'button_press_event': {0: <weakref at 0x117bcff98; to 'FigureCanvasTkAgg' at 0x117c0f860>,
  4: <matplotlib.cbook._StrongRef at 0x117c0f550>,
  5: <matplotlib.cbook._StrongRef at 0x119686dd8>},
 'scroll_event': {1: <weakref at 0x117be4048; to 'FigureCanvasTkAgg' at 0x117c0f860>},
 'key_press_event': {2: <weakref at 0x117be43c8; to 'FigureManagerTk' at 0x117c0fe10>},
 'motion_notify_event': {3: <weakref at 0x117be4438; to 'NavigationToolbar2Tk' at 0x117c0fe48>}}

What is interesting is that in this particular case, I've personally only added one event handler (button_press_event), but I've clearly got more events than that. What were seeing here is actually all of the events that are running in your backend too. Ever wondered how to disable some of those (like the keyboard shortcuts)? It is just a dictionary of events, and there is nothing stopping from just trashing them:

# Delete / remove the keyboard press event handlers.
fig.canvas.callbacks.callbacks.pop('key_press_event')

If you want to get a reference to the underlying function, a little bit of introspection suggests you can do something like:

In [37]: for cid, func_ref in fig.canvas.callbacks.callbacks['button_press_event'].items(): 
    ...:     func = func_ref()
    ...:     print(cid, func) 
Out[37]: 5 <function onclick at 0x114d7e620>