Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sliders unresponsive when created inside a function #3105

Closed
lebigot opened this issue Jun 2, 2014 · 12 comments · Fixed by #3132
Closed

Sliders unresponsive when created inside a function #3105

lebigot opened this issue Jun 2, 2014 · 12 comments · Fixed by #3132
Milestone

Comments

@lebigot
Copy link
Contributor

lebigot commented Jun 2, 2014

When a Slider is created inside a function, it is typically non-responsive (does not react to the mouse):

from matplotlib import pyplot
from matplotlib.widgets import Slider

sliders = []

def open_fig_with_slider():
    pyplot.figure()
    slider = Slider(pyplot.axes([0.25, 0.1, 0.65, 0.03]),
                    'Slider', 0, 1, valinit=0.5)
    # sliders.append(slider)

open_fig_with_slider()
pyplot.show()    # The slider is unresponsive unless append above is un-commented

The problem appears to be fixed by keeping a reference to the slider. The closest thing I've found in the documentation is that a canvas only keeps weak references to its callbacks (http://matplotlib.org/users/event_handling.html). However, the connection with sliders is not so obvious—if there is any.

If I'm not mistaken, this behavior is not documented, so it would be nice to add it to the documentation, if it is normal.

@tacaswell
Copy link
Member

During the Slider __init__ function one of it's instance methods is registered as a call-back to the canvas so the issue with weak-refs is exactly correct explanation of what is going on.

A quibble over language, I would say that the slider is 'non-responsive', not 'blocking' which is a term I associate with stopping execution of the whole program.

@tacaswell tacaswell added this to the v1.4.0 milestone Jun 2, 2014
@tacaswell tacaswell added doc and removed doc labels Jun 2, 2014
@lebigot lebigot changed the title Sliders blocked when created inside a function Sliders unresponsive when created inside a function Jun 2, 2014
@lebigot
Copy link
Contributor Author

lebigot commented Jun 2, 2014

OK, for "non-responsive", I updated the issue. Thanks.

It would be good if the documentation mentioned how to manage Sliders (widgets?) created in functions that make plots (which can be quite common in programs that make multiple figures).

I'm not sure if the method in the example I gave is a good idea (global reference kept), as I'm afraid that it may keep too many Matplotlib objects alive even when the figure is closed.

On the other hand, adding an instance attribute to the canvas, that holds the slider, probably defeats the purpose of using of a weak reference in the first place for one of its instance methods…

In any case, these questions show that documenting how to use sliders when creating plots in functions would be useful!

@tacaswell
Copy link
Member

I am planning to add a line to either the Silder doc or the top-level widget doc saying "In order for the widgets to remain responsive you must maintain a reference to them".

My suggestion on how to do this is to have the function return the figure, axes, and widget objects it creates:

def make_all_the_things():
    fig, ax_list = plt.subplots(1, 2)
    slide = Slider(ax_list[0], ...)
    return fig, ax_list, slide

@lebigot
Copy link
Contributor Author

lebigot commented Jun 3, 2014

Are you implying that users must keep references to these three objects in order to keep sliders responsive? the smaller the number of objects, the better… (more legible code, less memory consumed by figures that were closed but maintained in memory because of the references created by the caller, etc.).

I like the idea of returning the objects that must be kept in memory, because the caller can handle them locally (no need for a global variable, in general) and release them when/if needed.

@tacaswell
Copy link
Member

The only obligatory reference to keep is the slider, there are referances to the figure in the pyplot state machine and to the axes from the figure (and vice-versa). However to use the figure/axes in the OO style requires having the other two so might as well return them (as if you are writing scripts you should be using the OO style anyway ;) ).

@lebigot
Copy link
Contributor Author

lebigot commented Jun 4, 2014

I'm glad that only one reference (to the Slider) has to be kept. (Hopefully this does not lead to the figure being kept around if is closed, though, but that's a different issue.)

I would suggest that you make explicit in the documentation that "the only obligatory reference to keep is the slider", so that the code is "lighter". (I rarely feel the need to use the OO style, mostly because each figure is created in a single function, or in a well defined part of the code, so there is no ambiguity about what the code is doing, and it is also slightly more legible thanks to the use of fewer variables.)

Thank you for your help. :)

@tacaswell
Copy link
Member

I would argue that using pyplot (for anything but calling plt.subplots) always makes the code less readable as now the behavior is dependent on hidden global state (what the current figure/axes is).

I strongly suggest you write functions like this

def my_plotting(ax, *args, **kwargs):
    # do plotting/formatting/what ever
    return list_of_artists_added

fig, ax = plt.subplots(1, 1)
my_data = get_data()
my_data_filtered = filter_function(my_data)
arts = my_plotting(ax, my_data_filtered)

It is clear what is going on, the plotting logic is separate from the science/business logic, there is no ambiguity over which axes the artists will go to, and your my_plotting function is trivially re-usable. It can be directly used in custom mpl embeddings (which, yes, is a corner case but is an awesome corner case). If you want to do multi-axes figures all you have to do is

fig, (ax1, ax2) = plt.subplots(1, 2)  # or gridspec, or by hand
my_plotting(ax1, my_data1)
my_plotting(ax2, my_data2) 

@lebigot
Copy link
Contributor Author

lebigot commented Jun 4, 2014

I see what you mean, and I agree that your example is clear. However, I never personally came across a case where plotting in the current axes was unsafe (as long as it is documented):

def my_plotting(data):
    """
    Plot in the current axes…
    """
    # do plotting/formatting/whatever

my_data = get_data()
my_data_filtered = filter_function(my_data)
plt.figure()
my_plotting(my_data_filtered)

I agree that knowing in which axes the plotting function does its job generally requires one to check what the current axes are, and that in principle they can been changed as a side effect of some intervening functions. However, in many cases, the state-based approach is pretty clear, and the smaller number of variables arguably makes the code faster to read, and fewer variables need to be kept in working memory, so that's more legible, in my book. Again, though, I agree with you: there is a use for more a more explicit behavior.

@tacaswell
Copy link
Member

There is an entire genre of SO questions that are "how do I merge two figures" and "why are all of my plots going to the wrong axes?" which the answer boils down to "don't use the state machine". I think that we need to shift the way we introduce mpl to be OO first and then, by the way, the state-machine makes your life easy when working interactively.

I am also going to fall back to pep-20 (http://legacy.python.org/dev/peps/pep-0020/): "explicit is better than implicit"

@lebigot
Copy link
Contributor Author

lebigot commented Jun 5, 2014

I see. Then it makes sense to encourage Matplotlib users to use the more explicit approach.

A personal note: I do prefer "explicit is better than implicit"; I somehow managed, over a few years now, to be sheltered from the problems you quote even though I've been using writing less explicit code. :) I guess that being fully aware of the concept of current axes and documenting that some functions plot in them helps.

tacaswell added a commit to tacaswell/matplotlib that referenced this issue Jun 10, 2014
Added note to widget doc-strings that the user must maintain a reference
to the widgets to keep them responsive (if they get gc'd the call backs
go away).

Some random conversion to numpyboc

Closes matplotlib#3105
@tacaswell
Copy link
Member

@lebigot Please check that PR #3132 makes this clear enough.

@lebigot
Copy link
Contributor Author

lebigot commented Jun 10, 2014

Thank you. I left a couple of comments on small typos.

It would be useful to also add a sentence to the online documentation, in the introduction about widgets at http://matplotlib.org/api/widgets_api.html#gui-neutral-widgets. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants