ipympl
ipympl copied to clipboard
Mouse movement across canvas seems to trigger events even though all callbacks are cleared
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
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).
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.
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.
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.
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.
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.
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
?
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.