matplotlib / ipympl

Matplotlib Jupyter Integration
https://matplotlib.org/ipympl/
BSD 3-Clause "New" or "Revised" License
1.59k stars 226 forks source link

[BUG] Matplotlib FigureCanvas is shown at the bottom of an output instead of in a layout #474

Closed alexrockhill closed 2 years ago

alexrockhill commented 2 years ago

ipywidget layouts no longer accept matplotlib FigureCanvass as children because they are not widgets. If you wrap that in an output (see code below) as seems like a natural solution, you don't get the output in the layout, rather it is just appended to the bottom of the plot using sys.displayhook as far as I can tell. This prevents you from making a nice layout with multiple plots side-by-side for instance.

Versions

3.9.10 | packaged by conda-forge | (main, Feb  1 2022, 21:28:27) 
[Clang 11.1.0 ]
ipympl version: 0.9.1
Selected Jupyter core packages...
IPython          : 7.31.1
ipykernel        : 6.9.1
ipywidgets       : 7.7.1
jupyter_client   : 7.1.2
jupyter_core     : 4.9.2
jupyter_server   : 1.18.0
jupyterlab       : 3.4.3
nbclient         : 0.5.11
nbconvert        : 6.5.0
nbformat         : 5.4.0
notebook         : 6.4.8
qtconsole        : 5.2.2
traitlets        : 5.1.1
Known nbextensions:
  config dir: /Users/alexrockhill/software/anaconda3/envs/mne/etc/jupyter/nbconfig
    notebook section
      ipycanvas/extension  enabled 
      - Validating: OK
      ipyevents/extension  enabled 
      - Validating: OK
      jupyter-matplotlib/extension  enabled 
      - Validating: OK
      jupyter-js-widgets/extension  enabled 
      - Validating: OK
  config dir: /usr/local/etc/jupyter/nbconfig
    notebook section
      jupyter-js-widgets/extension  enabled 
      - Validating: OK
JupyterLab v3.4.3
/Users/alexrockhill/software/anaconda3/envs/mne/share/jupyter/labextensions
        ipycanvas v0.10.2 enabled OK
        jupyter-matplotlib v0.11.1 enabled OK
        ipyevents v2.0.1 enabled OK
        @jupyter-widgets/jupyterlab-manager v3.0.1 enabled OK (python, jupyterlab_widgets)

Example

from matplotlib.figure import Figure
from matplotlib.backends.backend_nbagg import FigureCanvas
from ipywidgets import HBox, Output, Label, Widget

class _MplCanvas(Output):

    def __init__(self, width, height, dpi):
        import matplotlib.pyplot as plt
        Output.__init__(self)
        with self:
            self.fig, self.ax = plt.subplots(figsize=(width, height), dpi=dpi)

canvas = _MplCanvas(5, 5, 96)
canvas.ax.plot(range(10))
vbox = HBox()
label = Label(value='test')
vbox.children = (canvas, label)
vbox

Test should come after canvas here:

Screen Shot 2022-06-30 at 5 45 06 PM

https://github.com/mne-tools/mne-python/pull/10803 Similar to https://github.com/matplotlib/ipympl/issues/203

alexrockhill commented 2 years ago

It turns out you just can't use Output, I should have read the comprehensive example more closely, the last part was very explanatory!

This works:

%matplotlib widget
import matplotlib.pyplot as plt
from ipywidgets import HBox, Output, Label, Widget, IntSlider
from ipympl.backend_nbagg import Canvas

plt.ioff()
fig = plt.figure()
plt.ion()
ax = fig.add_subplot(111)
ax.plot(range(10))    

slider = IntSlider(value=0, min=0, max=10)
HBox([slider, fig.canvas])

It's a bit tricky to abstract the canvas as a class but it works and that's great!