Ipython: %matplotlib inline doesn't work well with ipywidgets 6.0 interact

Created on 3 Mar 2017  ·  46Comments  ·  Source: ipython/ipython

It seems that in the jupyter notebook, %matplotlib inline doesn't play nice with the new ipywidgets 6.0 @interact anymore. As best as I can tell, the inline backend waits until the cell is completely finished before sending the display_data message with the image (see the post-execute hook registered at https://github.com/ipython/ipython/blob/7cde22957303ab53df8bd464ad5d7ed616197f31/IPython/core/pylabtools.py#L383). In ipywidgets 6.0, we've changed how interacts work to be more specific about the outputs we capture - we capture output messages sent during execution of the function, and display those in the interactive output. However, since the matplotlib inline backend sends its image after the function is executed, we just get a huge series of images in the output area of the cell.

import matplotlib.pyplot as plt
from ipywidgets import interact

%matplotlib inline
x = [1,2,5,4,3]
@interact(n=(2,5))
def f(n):
    plt.plot(x[:n])

This issue is about starting the discussion to see if there's a way for us to come up with a good solution that works well with the inline backend (which is useful) and also works well with the ipywidgets interact.

For example, if there was a way to tell the inline backend to flush its image immediately and not after a cell finished executing, that might be enough. This code works, for example (without the inline backend being enabled)

import matplotlib.pyplot as plt
from ipywidgets import interact

x = [1,2,5,4,3]
@interact(n=(2,5))
def f(n):
    plt.plot(x[:n])
    plt.show()

CC @SylvainCorlay, @tacaswell.

Most helpful comment

Hi all, I hadn't been following the technical details of this, but have run into the problem. Regardless of implementation details I think it is extremely important that matplotlib+interact "just works" as much as possible.

All 46 comments

This seems to work, but does anyone have a solution for a better way?

import matplotlib.pyplot as plt
from ipywidgets import interact
from ipykernel.pylab.backend_inline import flush_figures

%matplotlib inline
x = [1,2,5,4,3]
@interact(n=(2,5))
def f(n):
    plt.plot(x[:n])
    flush_figures()

CC @minrk, who worked on the inline mpl backend.

I don't think there is much we can do.

What heuristic would you use for displaying a figure ? You can't do it at all figures update as many code reuse figures, but it's common to have animation.

Special casing @interact to flush after each operation ?

Special casing @interact to flush after each operation ?

Good point, thanks for the suggestion. It doesn't feel very clean to do it (to special case one specific backend for matplotlib). Maybe I'll wait to see if it's a huge problem for users to explicitly flush output like in the above example. I mean, explicit is better than implicit and all that...

Edit: to summarize, I think I agree that there isn't much we can (or should) do...

It turns out that this is all much easier than I thought - I must have done something wrong when I tried testing this earlier. We don't need to call flush_figures, it's enough to just call the standard plt.show:

import matplotlib.pyplot as plt
from ipywidgets import interact

%matplotlib inline
x = [1,2,5,4,3]
@interact(n=(2,5))
def f(n):
    plt.plot(x[:n])
    plt.show()

I think that's perfectly reasonable to ask people to insert a show() command in their interact, so I'm closing this.

If we want to keep this behavior (and other potential post-execute outputs), perhaps the post-X hooks could be invoked explicitly in interact before output capturing is finished? In general, my expectation of calling a function via interact is that it behaves exactly the same as if I had called the function once in a cell.

I would expect people to be surprised by all of the interact-with-matplotlib examples to stop showing figures, since 'interactive' matplotlib generally means plt.show is unnecessary. Putting it in an interactive function probably shouldn't change that.

It reminds me a bit of requiring sys.stdout.flush() to see output, before the IOThread was introduced. It made sense why it was required when you thought about how things worked, but that didn't make it any less surprising because it didn't behave how people expected.

I don't think we should execute all post-execute handlers automatically (who knows what they will do - maybe lots of stuff!). How about having a flush_display hook? We'd flush it before starting the interact, and the flush it again just before ending it. That way it is exactly analogous to flushing stdout/stdin.

Well, or on the other hand, maybe it does make sense to call the post-execute, if we think of an interact really acting like a single cell all its own. But then we'd probably want to call post-execute before the interact was run as well as after, just in case the interact was in a cell with other stuff.

Reopening, since the conversation isn't closed yet...

I like the idea of a flush_display hook, and it would work in theory, though it would require coordinated releases of ipython and ipykernel and ipywidgets to define a new hook and switch the backend to using it.

if we think of an interact really acting like a single cell all its own

I think this is they key, and it's a big 'if'. It is how I think of it, but I'm not everyone (yet).

f we think of an interact really acting like a single cell all its own
I think this is they key, and it's a big 'if'. It is how I think of it, but I'm not everyone (yet).

A confusing thing about this is that if we had a cell with code, then an interact run, then some more code, the cell would actually be executed like three cells (i.e., post-execute run three different times). I think the flush_display makes more sense in that case.

Additional question: would mirrored output in JLab work?

@willingc widgets work with mirrored output.

@SylvainCorlay I know they work now, and folks in Ann Arbor love it. My question was would it work if changes are made as discussed above.

gotcha sorry for the noise.

In the long run, I think that an approach that should work better is the ipympl widgets backend to matplotlib.

However, we will need to make all of this more easily installed with the conf.d etc...

Yes, the mirrored output should work with the changed discussed above. Thanks for making sure!

A confusing thing about this is that if we had a cell with code, then an interact run, then some more code, the cell would actually be executed like three cells (i.e., post-execute run three different times).

Indeed, that would be a bit weird. Wouldn't it be two times, though? One for the cell as a whole and one for the first invocation of interact? Depending on how you think about it, this also makes sense: with @interact, you have two executions: one for the declaration, and another execution for the first invocation of the interacted-with function.

I don't think you would have more than one if you used @interact_manual, for instance (I'm not sure).

Wouldn't it be two times, though? One for the cell as a whole and one for the first invocation of interact?

Consider this code:

plt.plot([1,2,3])
@interact(n=(1,10))
def f(n):
    plt.plot([1,n])
plt.plot([4,5,6])

Since the plotting is cumulative, and you want to treat the interact as a separate cell unto itself, you need to flush the figure before the interact runs, then flush the figure inside the interact, and then once again at the end of the cell. This is exactly what we do with flushing stdout too.

Actually, I'm not sure what the user expects in the example above. What would you expect?

Just a thought: the example above isn't very logical; personally I would expect three plots where only one changes dynamically but if I were to do something like that I would do all the plot calls inside @interact.

Is there any reason not to call flush_figures after each .plot()?

@myyc it is a significant semantic change of the behavior. plot adds items on the plot....

I meant in the specific ipython inline behaviour, as opposed to matplotlib.

edit: I am sure it is a more common use case to have one interact per cell with that as "the only plotting function", as opposed to the case described above ...

I understand. Although I don't think that this should have a so different behavior.

The proper approach in the future will probably be to use the notebook backend or ipympl and edit the figure.

Implementing matplotlib-specific workarounds for the inline backend in ipywidgets' interact does not seem right.

In the code given by jason:

plt.plot([1,2,3])
@interact(n=(1,10))
def f(n):
    plt.plot([1,n])
plt.plot([4,5,6])

the expected output is a figure with a lot of lines..

Yes I completely agree. I temporarily fixed it that way (overriding pandas plotting methods) in my local setup, "waiting for a fix" :) Disregard that, it introduces a bunch of awful side effects.

I've seen this issue being bounced around a variety of github projects so I'm wondering what the best resolution path should be.

edit: why not write it this way then, way more explicit?

@interact(n=(1,10))
def f(n):
    plt.plot([1,2,3])
    plt.plot([1,n])
    plt.plot([4,5,6])

You can check out the ipympl package (https://github.com/matplotlib/jupyter-matplotlib), which is the matplotlib widget. ipympl is still pretty early stage, but should have a faster release schedule than matplotlib core, and earlier support for jupyterlab etc...

OK turns out that the override is a terrible idea. I'll look forward to a fix upstream :)

Just to contribute an uneducated user viewpoint: after updating to ipywidgets 6.0, I found that much of my widget code didn't work (due to the issue discussed here). I was able to fix it by adding calls to plt.show(), but I've also found that I have to be very careful about where I add such calls; it seems that sometimes calling this too soon causes later plots not to appear (even if plt.show() is called again). I haven't hunted this down enough to create a minimal example, but the current behavior seems unintuitive to me -- I am programming by trial and error to get around it.

What exact change in ipywidgets actually caused the new behavior? Are the post-execute hooks not being invoked at all, or are they still being invoked but their output is now discarded because the widget is being particular about which outputs are owned by the widget? If it's the latter, then it seems like isolating captured output to during the body of the function is not the right thing to do.

The post-execute hooks are being invoked (I'm pretty sure) - you can see this from doing:

import matplotlib.pyplot as plt
from ipywidgets import interact
%matplotlib inline

@interact(n=(0,10))
def f(n):
    plt.plot([0,n])

and moving the slider. The plots will be appended to the cell - the post execute hooks are sending the display-data messages. The interact executions indeed are being particular about only capturing output happening during the function invocation. I think that's the right thing to do. For example, two different interacts might share the exact same slider instance:

import matplotlib.pyplot as plt
from ipywidgets import interact, IntSlider
%matplotlib inline

x = IntSlider()
@interact(n=x)
def f(n):
    plt.plot([0,n])
    plt.show()
@interact(n=x)
def f(n):
    plt.plot([0,n,0,n])
    plt.show()

When the single slider is tweaked (in either UI), both interacts would show the slider moving and both interact functions would run in the same execution call. It would be wrong for both interacts to be trying to capture any output from a post-execute hook. I think the flaw here is that we are using post-execute to generate output, and that's just too coarse of a hammer. We should have a finer-grained method for flushing output generated in a block of code, which may be called multiple times in a single execution.

We should have a finer-grained method for flushing output generated in a block of code

For the matplotlib plotting, this flushing happens when doing plt.show

I have to be very careful about where I add such calls; it seems that sometimes calling this too soon causes later plots not to appear

I am really interested in seeing examples where this happens. We want to make the behavior intuitive and understandable.

I have to be very careful about where I add such calls; it seems that sometimes calling this too soon causes later plots not to appear

I am really interested in seeing examples where this happens. We want to make the behavior intuitive and understandable.

To elaborate, I think you should be able to get the equivalent of the previous behavior just by adding plt.show() as the last line of any interact function that is doing plotting. I'm very interested in seeing examples where this doesn't work.

@jasongrout Thanks for looking at this. The code where I was hitting this is rather complicated, and it's possible that I was doing something else wrong. If I see it again I will try to isolate it and provide an example.

@jasongrout After a bit more digging, I think what I've been running into is a property of the Jupyter notebook itself, not of widgets (but it interacts with widgets now because we are forced to call plt.show()).

If one creates a plot in a notebook cell and calls plt.show(), then later modifications to the figure do not show up. Here's a minimal example:

%matplotlib inline
import matplotlib.pyplot as plt

def plotfun(t):
    fig, ax = plt.subplots(1,1)
    ax.plot([0,t],[0,t])
    plt.show()
    ax.plot([t,0],[0,t])
    plt.show()

plotfun(2)

Only the first line plot appears.

Here's an even simpler example. Two cells, without the %matplotlib inline backend. Perhaps @tacaswell could shed some light on why the second plt.show doesn't show a plot.

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1,1)
ax.plot([0,1],[0,1])
plt.show()

shows plot

ax.plot([1,0],[0,1])
plt.show()

doesn't show anything

Confirmed on my machine with latest stable jupyter, ipython, ipywidgets, matplotlib, python 3.

Confirmed here as well.

@jasongrout That is the expected behavior because in the first cell you create a figure (and canvas and axes) and then display it. In the second cell you then add an line to the existing axes (in the existing figure). With %matplotlib qt or ipympl (or any of the full GUIs) the plot _should_ be update with the second line. I don't expect this to work with inline as you only get one shot to update it (which is why I am very much not a fan of inline, it throws out _all_ of the interactive functionality).

@tacaswell Does this behavior of inline also make sense for the earlier example I gave, which is all in one cell? Here it is again:

%matplotlib inline
import matplotlib.pyplot as plt

def plotfun(t):
    fig, ax = plt.subplots(1,1)
    ax.plot([0,t],[0,t])
    plt.show()
    ax.plot([t,0],[0,t])
    plt.show()

plotfun(2)

I think that on plt.show the inline backend renders its self to a png and then de-registers the figure from pyplot so it will not be rendered again on the next show, even if it is in the same cell, however multiple calls to axes methods without extra shows should work.

Is there a version of ipywidgets and/or matplotlib I can roll back to in order to get my interactive widgets to work again?

@jasongrout It looks from the code like I might want to set InlineBackend.close_figures to False. But I'm not sure how to set that atrribute -- do I need to use some sort of magic?

(that question sounds funny but I mean it literally)

https://github.com/jupyter-widgets/ipywidgets/issues/1457 raised this issue again. @ellisonbg - what if we implement the flush_figures workaround I mention above - call flush_figures before and after an interact runs, to flush out any plots before the interact, and flush out anything plotted during the interact? I think we may have to check to see if we are running in the inline mpl backend.

It's a huge kludge, but maybe it's important enough to do it anyway. Perhaps we can also detect if we are actually flushing a figure and issue a warning asking people to do an explicit show() call?

Hi all, I hadn't been following the technical details of this, but have run into the problem. Regardless of implementation details I think it is extremely important that matplotlib+interact "just works" as much as possible.

With plt.show() or flush_figures(), if the notebook is refreshed (without restarting the kernel), the figure is gone even with Save Notebook with Widgets. Without these mitigations, this is not the case although then there is still the issue of multiple plot outputs. Is this intended? The same applies if the kernel is shut down, whereas with older versions of ipywidgets the final state of the figure would be saved into the notebook akin to a non-interactive plot, which was quite handy.

Sorry if this is out of scope, or if I'm doing something wrong. Running ipywidgets 6.0.0, matplotlib 2.0.2, notebook 5.0.0, and python 3.6.1.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

frenzymadness picture frenzymadness  ·  3Comments

okomarov picture okomarov  ·  3Comments

sataliulan picture sataliulan  ·  4Comments

quchunguang picture quchunguang  ·  3Comments

ghost picture ghost  ·  4Comments