在IPython笔记本小部件中交互式图表的放置

11

我有两个图表,希望能够用IPython笔记本小部件进行交互。下面的代码是我尝试做的简化示例。

import matplotlib.pyplot as plt
import IPython.html.widgets as wdg

def displayPlot1(rngMax = 10):
    plt.figure(0)
    plt.plot([x for x in range(0, rngMax)])

wdg1 = wdg.interactive(displayPlot1, rngMax = wdg.IntSlider(20))

def displayPlot2(rngMax = 10):
    plt.figure(1)
    plt.plot([x**2 for x in range(0, rngMax)])

wdg2 = wdg.interactive(displayPlot2, rngMax = wdg.IntSlider(10))

wdg.ContainerWidget([wdg.HTML("""<h1>First Plot</h1>"""),
                     wdg1, 
                     wdg.HTML("""<h1>Second Plot</h1>"""), 
                     wdg2])

第一个问题是它首先显示所有的小部件,然后在最后一个之后连续显示两个图:

title1
widget1
title2
widget2
plot1
plot2

我想要:

title1
widget1
plot1    
title2
widget2
plot2

同时似乎整个输出都会在我触摸任何滑块时被覆盖,并且仅显示一个图(正在更改的图)。

我该如何解决这个问题?(如果我将它们分成两个不同的单元格,我可能可以解决它,但我计划做一些更复杂的事情,最终需要在一个单元格中完成)


1
看到你发起了一个悬赏,所以我想你可能仍然对一个更详细的答案感兴趣。我在下面添加了一些代码,应该能够说明如何做这个。我来得有点晚了,但或许你仍可以为此奖励一些声望值。 - Markus Schanta
1个回答

6

IPython Notebook会在任何输出之前显示小部件。您可以做的一件事是将图表放置在HTML小部件中。这可以放置在相对于其他小部件的任何位置。

但是,如果您这样做,您需要明确地将图表放置在HTML小部件中。这可能有点棘手,但一个快速的解决方案是将图表的字节字符串保存到缓冲区,然后将字节字符串放入图像标记中。

这里是一个例子(此处的Gist):

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

def handle_input1(slope):
    plt.figure()
    plt.plot([x * slope for x in range(0, 11)])
    plt.ylim((0,10))
    output1HTML.value = plot_to_html()
    plt.close()

def handle_input2(curvature):
    plt.figure()
    plt.plot([x * x * curvature for x in range(0, 11)])
    plt.ylim((0,100))
    output2HTML.value = plot_to_html()
    plt.close()

def plot_to_html():
    # write image data to a string buffer and get the PNG image bytes
    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    buf.seek(0)
    return """<img src='data:image/png;base64,{}'/>""".format(base64.b64encode(buf.getvalue()).decode('ascii'))

plt.ioff()

heading1HTML = widgets.HTML("""<h1>Slope</h1>""")
input1Float = widgets.FloatSlider(value=0.5, min=0.0, max=1.0, step=0.01, description="slope: ")
widgets.interactive(handle_input1, slope=input1Float)
output1HTML = widgets.HTML()

heading2HTML = widgets.HTML("""<h1>Curvature</h1>""")
input2Float = widgets.FloatSlider(value=0.5, min=0.0, max=1.0, step=0.01, description="curvature: ")
widgets.interactive(handle_input2, curvature=input2Float)
output2HTML = widgets.HTML()

display(widgets.Box([heading1HTML, input1Float, output1HTML, heading2HTML, input2Float, output2HTML]))

handle_input1(input1Float.value)
handle_input2(input2Float.value)

编辑1: IPython小部件已移至IPython.html; 相应地更新了代码。

编辑2: 根据最新的IPython小部件文档,使用widgets.interactive; 再次更新IPython小部件位置; 添加ASCII编码。

编辑3: 截至2021年,IPython小部件可能不再是交互式绘图的首选工具。使用Altair可能是现在生成交互式图表的更好方式。


1
IPython小部件再次移动,但现在仅作为警告。更重要的是,在Python 3.x中,您必须对从buf.getvalue()返回的字节字符串进行.decode('ascii')解码。 - gboffi
1
为了更正自己,ascii 是由 base64.b64encode() 返回的值,所以我建议的修改是 base64.b64encode(buf.getvalue()).encode('ascii'),这在 Python2 中也适用,在 Python3 中也需要。 - gboffi
感谢您的反馈,@gboffi。我已相应地更改了示例,并查看了最新的IPython小部件文档。似乎widgets.interactive()是向小部件注册观察者的首选方式。我的这台机器上没有python3,您可以检查一下是否会出现任何警告吗? - Markus Schanta
翻译如下:哎呀,第一条注释是.decode();第二条注释是.encode()。当然,正确的答案是.decode(),就像base64.b64encode(buf.getvalue()).decode('ascii')里面那样。除了这个问题(完全是我的错),你更新后的代码在IPython/Jupyter 4和Pyhon 3.5上运行得非常好。非常感谢,再见。 - gboffi
以上代码在运行于Jupyter Lab 2.2.6时没有任何输出。我想IPython小部件自这些评论以来已经有了很大的进步。希望能得到一些帮助让它正常工作 :D - user2253546
截至2021年,IPython小部件可能不再是交互式绘图的首选工具。使用Altair可能是现在生成交互式图表的更好方法。我已经在我的答案中添加了一条注释。 - Markus Schanta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接