Home > Blockchain >  Controlling order of display in ipywidgets Vbox (when matplotlib widget is used)?
Controlling order of display in ipywidgets Vbox (when matplotlib widget is used)?

Time:11-25

This is so damn irritating... using:

Python 3.8.10 (default, Sep 28 2021, 16:10:42) [GCC 9.3.0]
matplotlib 3.5.0
ipywidgets 7.6.5
jupyter 1.0.0

So, consider this example:

import IPython.display
from IPython.display import display
from ipywidgets import widgets, Layout

%matplotlib widget
import matplotlib as mpl
import matplotlib.pyplot as plt

widget_01 = widgets.HTML("<p>Hello, world</p>")
widget_02 = widgets.Output(layout=Layout(width='100%'))
widget_03 = widgets.Output(layout=Layout(width='100%'))

with widget_02:
    IPython.display.clear_output(True)
    display(widgets.HTMLMath("<p>Try equation: $x_y = y_x$</p>"))
    
myvbox = widgets.VBox([
    widget_01,
    widget_02,
    widget_03
],)
display(myvbox)

with widget_03:
    IPython.display.clear_output(True)
    display(widgets.HTMLMath("<p>In the next episode...</p>"))

So, I have a "Vbox", which has three elements, "widget_01", "widget_02", "widget_03"; these three elements are put in a list, which (unlike dict) has an order, so I can count on "widget_01" being "first", "widget_02" being "second", and so on. And since this is a "VBox", that is, Vertical Box, I'd expect the "first" item in the list to be rendered on top, the "second" item below it (that is, in the middle), and so on.

And indeed, that is how the output looks like in the simplest case:

vbox_order_ok

Ok, cool - now, let's just make "widget_02" into matplotlib widget:

import IPython.display
from IPython.display import display
from ipywidgets import widgets, Layout

%matplotlib widget
import matplotlib as mpl
import matplotlib.pyplot as plt

widget_01 = widgets.HTML("<p>Hello, world</p>")
widget_02 = widgets.Output(layout=Layout(width='100%'))
widget_03 = widgets.Output(layout=Layout(width='100%'))

with widget_02:
    IPython.display.clear_output(True)
    fig = plt.figure(figsize=(10,1), dpi=90)
    fig.canvas.toolbar_visible = False
    ax = fig.add_subplot(111)
    ax.plot([0,1,2], [0,1,2])
    
myvbox = widgets.VBox([
    widget_01,
    widget_02,
    widget_03
],)
display(myvbox)

with widget_03:
    IPython.display.clear_output(True)
    display(widgets.HTMLMath("<p>In the next episode...</p>"))

Well, now - for whatever reason - "widget_02" (which became a plot) is not in the middle anymore, but is instead last (at bottom):

vbox_order_bad

I mean, I sure update "widget_03" after I update the plot - but I'd expect the VBox to keep the order of widgets?

So - how can I have a Matplotlib widget rendered in the order it is listed into an ipywidgets VBox, even if I update a different widget in the same VBox after I have rendered the plot?

CodePudding user response:

Calling plt.show() after ax.plot([0,1,2], [0,1,2]) solves your issue for me. See code below:

import IPython.display
from IPython.display import display
from ipywidgets import widgets, Layout

import matplotlib as mpl
import matplotlib.pyplot as plt

widget_01 = widgets.HTML("<p>Hello, world</p>")
widget_02 = widgets.Output(layout=Layout(width='100%'))
widget_03 = widgets.Output(layout=Layout(width='100%'))

with widget_02:
    IPython.display.clear_output(True)
    fig = plt.figure(figsize=(10,1), dpi=90)
    fig.canvas.toolbar_visible = False
    ax = fig.add_subplot(111)
    ax.plot([0,1,2], [0,1,2])
    plt.show()
    
myvbox = widgets.VBox([
    widget_01,
    widget_02,
    widget_03
],)
display(myvbox)

with widget_03:
    IPython.display.clear_output(True)
    display(widgets.HTMLMath("<p>In the next episode...</p>"))

And the output looks like that:enter image description here

CodePudding user response:

Ok, I found a solution here: vbox_layout_ok

... the following corrected code can be used:

import IPython.display
from IPython.display import display
from ipywidgets import widgets, Layout

%matplotlib widget
import matplotlib as mpl
import matplotlib.pyplot as plt

widget_01 = widgets.HTML("<p>Hello, world</p>")
widget_02 = widgets.Output(layout=Layout(width='100%'))
widget_03 = widgets.Output(layout=Layout(width='100%'))

with widget_02:
    widget_02.clear_output(wait=True)
    plt.ioff() # "turn off interactive mode so figure doesn't show"
    fig = plt.figure(figsize=(10,1), dpi=90)
    fig.canvas.toolbar_visible = False
    ax = fig.add_subplot(111)
    ax.plot([0,1,2], [0,1,2])
    plt.ion() # "figure still doesn't show"
    display(fig.canvas) # "It's the canvas attribute that is the interactive widget, not the figure"
    
myvbox = widgets.VBox([
    widget_01,
    widget_02,
    widget_03
],)
display(myvbox)

with widget_03:
    IPython.display.clear_output(True)
    display(widgets.HTMLMath("<p>In the next episode...</p>"))
  • Related