控制 ipywidgets Vbox 中的显示顺序(使用 matplotlib 小部件时)?

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

这太他妈烦人了...使用:

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

所以,考虑这个例子:

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>"))

所以,我有一个“Vbox”,它有三个元素,“widget_01”,“widget_02”,“widget_03”;这三个元素放在一个列表中,它(不像字典)有一个顺序,所以我可以指望“widget_01”是“第一”,“widget_02”是“第二”,等等.由于这是一个“VBox”,即 Vertical Box,我希望列表中的“第一个”项目呈现在顶部,它下面的“第二个”项目(即在中间),以及等等。

事实上,这就是最简单情况下的输出结果:

好的,很酷 - 现在,让我们将“widget_02”变成 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>"))

好吧,现在 - 无论出于何种原因 - “widget_02”(成为情节)不再位于中间,而是最后(底部):

我的意思是,我确定 update “widget_03” 在我更新绘图后 - 但我希望 VBox 保持小部件的顺序?

所以 - 如果我在同一个 VBox 中更新不同的小部件,我如何才能按照它在 ipywidgets VBox 中列出的顺序呈现 Matplotlib 小部件,even渲染情节?

好的,我在这里找到了解决方案:Displaying a previously created Matplotlib figure in a widget · Issue #203 · matplotlib/ipympl,看来...

事实证明问题不是“控制位置”本身,而是:

  • 事实上 fig = plt.figure... 调用实际上会自行产生输出,而不管 (with) 输出上下文;可以通过 plt.ioff()
  • 禁用 matplotlib 交互模式来防止
  • 事实上,要显示交互式 matplotlib 小部件,我们应该 display(fig.canvas)(不仅仅是 display(fig)

所以,在 OP 中,甚至没有 display(fig) - 所以 matplotlib 小部件的整个输出都是由于 fig = plt.figure... 调用。

因此,要获得所需的输出,如 VBox 中指定的那样(widget_01 文本在顶部,widget_02 绘图在中间,widget_03 文本在底部):

...可以使用以下更正后的代码:

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>"))

ax.plot([0,1,2], [0,1,2]) 之后调用 plt.show() 为我解决了你的问题。 请参阅下面的代码:

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>"))

输出如下所示: