matplotlib / matplotlib

matplotlib: plotting with Python
https://matplotlib.org/stable/
19.99k stars 7.56k forks source link

ipywidgets with v3.3x #18741

Closed prisae closed 3 years ago

prisae commented 3 years ago

Bug report

Bug summary

Interactive code (Jupyter Notebook using %matplotlib notebook) worked up to v3.2.x, stops working for v3.3.x. Tested on Firefox and Chrome.

Code for reproduction

import ipywidgets as widgets
import matplotlib.pyplot as plt
from IPython.display import display

%matplotlib notebook

class MWE:

    def __init__(self):
        self.fig, self.ax = plt.subplots(1, 1)
        self.ax.set_xlim([-5, 105])
        self.ax.set_ylim([-500, 10500])
        self.create_widget()
        self.draw()

    def create_widget(self):
        value = widgets.interactive(
            self.update_value,
            value=widgets.IntSlider(value=50, min=0, max=100))
        display(value)

    def draw(self):
        self.ax.plot(self.value, self.value**2, 'o')

    def update_value(self, value):
        self.value = value
        self.draw()

MWE()

Actual outcome Just an empty figure, moving the slider has no effect, no warning thrown either.

Expected outcome More and more dots should be printed when moving the slider.

Matplotlib version

Installed with mamba from conda-forge.

It might be related to https://github.com/matplotlib/matplotlib/issues/18481, but it happens for Firefox and Chrome on my machine. Also, the mentioned workaround (https://github.com/matplotlib/matplotlib/issues/18481#issuecomment-692337027) does not help.

ianhi commented 3 years ago

There's probably a bug a here, one that I don't understand, but you can work around this by using ipywidgets in a more manual fashion.

The issue is probably (maybe?) something to do with a weird sideeffect of using interactive which tries to display things. For this kind of thing I'd recommend just directly observeing the slider. Like so: (this works for me on mpl 3.3.2)

import numpy as np
import ipywidgets as widgets
import matplotlib.pyplot as plt
from IPython.display import display

%matplotlib notebook

class MWE:

    def __init__(self):
        self.fig, self.ax = plt.subplots(1, 1)
        self.ax.set_xlim([-5, 105])
        self.ax.set_ylim([-500, 10500])
        self.create_widget()
        self.draw()

    def create_widget(self):
        self.slider = widgets.IntSlider(value=50, min=0, max=100)
        self.slider.observe(self.update_value, names='value')
        display(self.slider)
        self.value = 0

    def draw(self):
        self.ax.plot(self.value, self.value**2, 'o')

    def update_value(self, change):
        self.value = change['new']
        self.draw()

MWE()

If you're including matplotlib in a layout with ipywidgets you may also want to use ipympl as that makes matplotlib plots widgets so they can be explicitly positioned in a layout. See the examples here: https://github.com/matplotlib/ipympl/blob/master/examples/ipympl.ipynb

tacaswell commented 3 years ago

If I run that code and try to use the pan or zoom tools I get a lot of

[IPKernelApp] WARNING | No such comm: 4f2b7240901a460b95631937d0b342c9
[IPKernelApp] WARNING | No such comm: 4f2b7240901a460b95631937d0b342c9
[IPKernelApp] WARNING | No such comm: 4f2b7240901a460b95631937d0b342c9
[IPKernelApp] WARNING | No such comm: 4f2b7240901a460b95631937d0b342c9
[IPKernelApp] WARNING | No such comm: 4f2b7240901a460b95631937d0b342c9
[IPKernelApp] WARNING | No such comm: 4f2b7240901a460b95631937d0b342c9
[IPKernelApp] WARNING | No such comm: 4f2b7240901a460b95631937d0b342c9
[IPKernelApp] WARNING | No such comm: 4f2b7240901a460b95631937d0b342c9

which makes me think that something has gone sideways with the display logic (and our js hacking and the ipywidget display work are colliding) which results in the comm (how the JS front end talks to the python backend) being torn down prematurely.

prisae commented 3 years ago

Thanks @ianhi , that works for this example. However, the real examples has various sliders, dropdowns, buttons, that are aligned horizontally etc. Does that approach work for a complex setup?

ianhi commented 3 years ago

@prisae yup it should. You may need to be careful to use one of the ipywidgets layouts either the highlevel AppLayout or nest VBox and HBox elements. Two examples for you:

  1. the above linked examples for ipympl shows how to use AppLayout
  2. something I made awhile ago https://github.com/ianhi/AC295-final-project-JWI/blob/4f1831a0d9cc6228ef3b419ea77157a4ef9a229a/lib/labelling.py#L152
    • messy in parts but shows moderate complexity layout of ipywidgets + maptlotlib and linking them makes this: image
prisae commented 3 years ago

OK, that is definitely one route. But it would include rewriting the GUI to some extent. I am sure that there are other "Jupyter-Apps" that will break when updating to matplotlib v3.3, so it would be nice to find out what is breaking it...

ianhi commented 3 years ago

The other thing to note is that if you change %matplotlib notebook to %matplotlib ipympl your example works. and it will also work in jupyter lab not just notebook.

prisae commented 3 years ago

Ha, fantastic, thanks! I tried %matplotlib widget, but that didn't work.

Thanks. For me that is solved. Not sure if you want to keep the issue open though to track the problem down or not.

tacaswell commented 3 years ago

%matplotlib widget and %matplotlib ipympl are aliases for each other https://github.com/ipython/ipython/blob/f8c9ea7db42d9830f16318a4ceca0ac1c3688697/IPython/core/pylabtools.py#L14-L31 so either should work.

There are issues switching the backends, typically if you want to restart your kernel too.

prisae commented 3 years ago

My bad. %matplotlib widget does indeed work now, and I thought I did restart. But it might have interfered with all the other things I tried...

QuLogic commented 3 years ago

This is a duplicate of #18638, and will be fixed in 3.3.3.