ipympl icon indicating copy to clipboard operation
ipympl copied to clipboard

Mouse movement across canvas seems to trigger events even though all callbacks are cleared

Open hoechenberger opened this issue 4 years ago • 8 comments

Hello, I must be missing something fundamental here, but the issue is the following:

Consider this MWE:

%matplotlib widget

import matplotlib.pyplot as plt

plt.ioff()
fig, ax = plt.subplots(figsize=(2, 2))
fig.canvas.toolbar_visible = False
fig.canvas.header_visible = False
fig.canvas.resizable = False

# Remove all callbacks
fig.canvas.callbacks.callbacks.clear()
fig.callbacks.callbacks.clear()
ax._propobservers = {}

ax.plot([1, 2, 3])
display(fig.canvas)

As expected, it draws a simple figure without header and toolbar into the notebook. However, even though I removed all callbacks that might be associated with this figure, its canvas, and the axes, moving the mouse cursor across the figure causes communication between front- and backend, which results in considerable data transfer (and, hence, jerkiness) when used over the internet. See this screen recording that demonstrates the issue:

https://youtu.be/M5mMzIGXT1s

It can also be seen that mouse clicks (indicated by the black circle) inside the canvas trigger communication as well.

How can I disable this behavior?

My system specifications:

  • ipympl 0.5.6
  • JupyterLab 2.0.1
  • Browser: Safari 13.1
  • OS: macOS Catalina 10.15.4

hoechenberger avatar Apr 10 '20 13:04 hoechenberger

Hello, thanks for opening an issue!

Indeed your code will not cancel the communication between the back-end and the front-end. I did not know there was an API for that in Matplotlib, and it's not plugged to the widgets communication AFAIK.

@tacaswell do you think doing fig.canvas.callbacks.callbacks.clear() should cancel communication? If yes, should it only cancel mouse-related communication or all communication (resize, redraw etc).

martinRenou avatar Apr 10 '20 13:04 martinRenou

Hello @martinRenou, thanks for your quick response!

Just to clarify: I do not wish to end all communication with the backend; I just want to only send the events to the backend that I care about. In my specific use case, I don't care about the mouse movement events, because I don't use them – if I wanted to, I would create a respective callback manually. The above example is just to demonstrate that even though I believed I removed all callbacks explicitly, there's still communication going on.

Edit: Revised the above comment.

hoechenberger avatar Apr 10 '20 13:04 hoechenberger

Thanks for clarifying!

I am wondering how other Matplotlib back-ends are behaving, it might be good to follow the same behavior as much as possible.

martinRenou avatar Apr 10 '20 13:04 martinRenou

I am wondering how other Matplotlib back-ends are behaving

No idea, I'm relatively inexperienced when it comes to Matplotlib internals. In any case, since ipympl is specifically designed for Jupyter Notebook use, and since Jupyter Notebooks are often deployed online on remote servers, behavior that might be unproblematic for other (locally running) backends could become an issue due to network latency and data transfer time. This just to say that: Even if other backends do act like I demonstrated in my MWE, ipympl should probably opt for a slightly different approach that's more responsive and generates fewer data transfers.

hoechenberger avatar Apr 10 '20 13:04 hoechenberger

do you think doing fig.canvas.callbacks.callbacks.clear() should cancel communication? If yes, should it only cancel mouse-related communication or all communication (resize, redraw etc).

From my naïve user's perspective, if I ask ipympl to clear all callbacks, I'd expect it to clear all callbacks :) If I wish to retain some, I need to be more careful and only remove those that I don't want to keep.

hoechenberger avatar Apr 10 '20 13:04 hoechenberger

Your code is clearing the python side callbacks, but the communication is being instigated by the js side.

The main selling point of the widget based backend is that it is interactive, which means we need to send information back to the backend. If you do not want interactive figures you should probably be using the inline backend which gives you static images.

tacaswell avatar Apr 10 '20 18:04 tacaswell

Thanks for your response, @tacaswell.

I do want interactive figures. I just don't need interaction based on mouse-move events. They produce way too much communication with the backend, which causes ipympl plots to be a very choppy, unpleasant experience when used over the internet because of the involved round-trip times. Disabling a callback should not send those events to the backend. Isn't there some way to achieve this with ipympl?

hoechenberger avatar Apr 10 '20 18:04 hoechenberger

The way things are built, the GUI/js part of the "backend" is responsible for capturing user input (either from the OS or from the browser) that are then converted to the Matplotlib internal Event objects, which are then in turn pushed through the callback system. However, there is no mechanism to suppress capturing the user interaction and event generation if there are no callbacks.

To disable the network traffic you will need to do something on the js side of the house.

I have had (but not shared anywhere public) some half-baked thoughts that we should send a (low-res?) pre-computed map of screen pixel -> data coordinate so that the coordinates can be updated purely on the client side.

I think the zoom box is already handled on the client side, but communication in unavoidable if you want pan to work.

tacaswell avatar Apr 10 '20 22:04 tacaswell