Matplotlib交互的IPython Notebook小部件
我想用ipython notebook里的小工具来给内嵌的matplotlib图表增加一些互动性。
一般来说,图表可能会比较复杂,我只想更新图表中的某个特定部分。我知道小工具有一个内置的限制功能,可以防止过多请求涌入内核,但如果图表更新需要30秒,我可不想等那么久才更新一条线。
通过阅读示例笔记本,我创建了一个基本的例子,在mpl坐标轴上添加了一个十字光标(由两个滑块控制)。
问题是,图形显示了两次。以下是代码(单元格1):
fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])
... 图形显示了 ...,单元格2(编辑:感谢Thomas K的改进):
vline = ax.axvline(1)
hline = ax.axhline(0.5)
def set_cursor(x, y):
vline.set_xdata((x, x))
hline.set_ydata((y, y))
display(fig)
最后(单元格3):
interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))
再次显示了带有小工具的图形。
所以问题是:
- 我怎么才能阻止第一次图形显示?
- 这样做对吗?还是有更好的方法?
编辑
我找到一个ipython配置选项,按照这个笔记本的说法,可以阻止图形显示。
%config InlineBackend.close_figures = False
虽然示例笔记本可以工作,但我不知道如何单独使用这个选项(不使用链接示例中的上下文管理类)来隐藏图形显示。
编辑2
我找到了一些关于InlineBackend.close_figures
可配置项的文档。
编辑3
受到@shadanan回答的启发,我想澄清一下我的目的,是在现有图形上添加一个光标,而不是在每次光标移动时都重新绘制图表。将三个单元合并成一个单元:
fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])
vline = ax.axvline(1)
hline = ax.axhline(0.5)
def set_cursor(x, y):
vline.set_xdata((x, x))
hline.set_ydata((y, y))
display(fig)
interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))
理论上“应该”可以工作,但实际上并不行。第一次执行单元时显示了两个图形。经过小工具交互后只显示一个图形。这就是需要像@shadanan回答中那样的解决方法的“奇怪行为”。ipython的开发者能对此做个评论吗?这是个bug吗?
3 个回答
你可以通过一种非常简单的方法来实现这个,使用新一点的 notebook
后端。
%matplotlib notebook
import matplotlib.pyplot as plt
from IPython.html.widgets import interactive
fig, ax = plt.subplots()
ax.plot(range(5))
vline = ax.axvline(1, color='k')
hline = ax.axhline(0.5, color='k')
def set_cursor(x, y):
vline.set_xdata((x, x))
hline.set_ydata((y, y))
ax.figure.canvas.draw_idle()
然后在一个单独的单元格中:
interactive(set_cursor, x=ax.get_xlim(), y=ax.get_ylim())
每次你移动光标时,这个方法仍然会重新绘制整个图形,因为 notebook
目前不支持“blitting”技术(这个功能正在开发中,详细信息可以查看 https://github.com/matplotlib/matplotlib/pull/4290)。
我有一个临时的解决办法,只能显示一个图形。问题在于代码中有两个地方会生成图形,而我们其实只想要第二个,但不能简单地去掉第一个。这个解决办法是第一次执行时用第一个,之后的执行就用第二个。下面的代码通过一个初始化的标志来在两者之间切换:
%matplotlib inline
import matplotlib.pyplot as plt
from IPython.html.widgets import interact, interactive, fixed
from IPython.html import widgets
from IPython.display import clear_output, display, HTML
class InteractiveCursor(object):
initialized = False
fig = None
ax = None
vline = None
hline = None
def initialize(self):
self.fig, self.ax = plt.subplots()
self.ax.plot([3,1,2,4,0,5,3,2,0,2,4])
self.vline = self.ax.axvline(1)
self.hline = self.ax.axhline(0.5)
def set_cursor(self, x, y):
if not self.initialized:
self.initialize()
self.vline.set_xdata((x, x))
self.hline.set_ydata((y, y))
if self.initialized:
display(self.fig)
self.initialized = True
ic = InteractiveCursor()
def set_cursor(x, y):
ic.set_cursor(x, y)
interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01));
我认为这应该算是一个bug。我用面向对象的接口试过,结果也是同样的问题。
这个解决方案其实非常简单。为了避免显示第一个图形,我们只需要在调用 interact
之前加上一个 close()
的命令。
回想一下问题中的例子,像这样的单元格将正确显示一个交互式图形(而不是两个):
fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])
plt.close(fig)
vline = ax.axvline(1)
hline = ax.axhline(0.5)
def set_cursor(x, y):
vline.set_xdata((x, x))
hline.set_ydata((y, y))
display(fig)
interact(set_cursor, x=(1, 9, 0.01), y=(0, 5, 0.01))
一个更简洁的方法是定义一个叫 add_cursor
的函数(可以在单独的单元格或脚本中):
def add_cursor(fig, ax):
plt.close(fig)
vline = ax.axvline(1, color='k')
hline = ax.axhline(0.5, color='k')
def set_cursor(x, y):
vline.set_xdata((x, x))
hline.set_ydata((y, y))
display(fig)
interact(set_cursor, x=ax.get_xlim(), y=ax.get_ylim())
然后每当我们想添加一个交互式光标时,就调用它:
fig, ax = plt.subplots()
ax.plot([3,1,2,4,0,5,3,2,0,2,4])
add_cursor(fig, ax)