点击时没有事件连接

时间:2013-12-18 10:40:45

标签: python matplotlib

我尝试修改我在stackoverflow论坛上找到的一些代码(How can I plot the same figure standalone and in a subplot in Matplotlib?第一个答案)。

如果单击该子图(仅在画布上显示单击的子图),则基本上放大一个子图,并在再次单击时缩小(在画布上显示所有子图)。我尝试修改代码以通过几种方式将其调整到我的程序,但是我一直遇到同样的问题。正确创建带有子图的图形画布并输入缩放子图类,但它似乎与我的点击事件无关(它不会输入'on_click')。

我试图找出问题并尝试了一些修改,但继续遇到同样的问题。我无法使用我检索到的主题中显示的代码,因为它不适合我的程序的其余部分。

import numpy as np
from matplotlib import pyplot as plt

class ZoomingSubplots(object):
    ''' zoom to subplot if subplot is clicked, unzoom when clicked again'''
    def __init__(self, fig):
        print 'class entered'
        self.fig = fig       
        self.fig.canvas.mpl_connect('button_press_event', self.on_click)

    def zoom(self, selected_ax):
        for ax in self.axes.flat:
            ax.set_visible(False)
        self._original_size = selected_ax.get_position()
        selected_ax.set_position([0.125, 0.1, 0.775, 0.8])
        selected_ax.set_visible(True)
        self._zoomed = True

    def unzoom(self, selected_ax):
        selected_ax.set_position(self._original_size)
        for ax in self.axes.flat:
            ax.set_visible(True)
        self._zoomed = False

    def on_click(self, event):
        print 'click event'
        if event.inaxes is None:
            return
        if self._zoomed:
            self.unzoom(event.inaxes)
        else:
            self.zoom(event.inaxes)
        self.fig.canvas.draw()

#make a figure with 9 random imshows

plots = 9         #number of plots
plotrows = 3      #subplot rows
plotcols = 3      #subplot columns

fig = plt.figure()
for i in range(plots):
    arr = np.random.rand(10,10)
    ax = fig.add_subplot(plotrows, plotcols, i+1)
    ax.imshow(arr, interpolation = 'nearest')
    ax.set_title('%s %i' % ('plot', i+1), fontsize = 10)

# connect with zoom class
ZoomingSubplots(fig)

plt.show()

将此调整为更简单的代码,您可以在其中注意到同样的问题:

import numpy as np
from matplotlib import pyplot as plt

class ZoomingSubplots(object):
    def __init__(self, fig):
        print 'class entered'
        self.fig = fig       
        self.fig.canvas.mpl_connect('button_press_event', self.on_click)

    def on_click(self, event):
        print 'click event'

#make a figure with 9 random imshows
fig = plt.figure()
for i in range(9):
    arr = np.random.rand(10,10)
    ax = fig.add_subplot(3, 3, i+1)
    ax.imshow(arr, interpolation = 'nearest')

# connect with zoom class
ZoomingSubplots(fig)

plt.show() 

1 个答案:

答案 0 :(得分:2)

正在发生的事情是matplotlib对回调等的“弱”引用的一些经典问题。在显示绘图之前,您的类实例被垃圾收集。


因为pylab状态机必须是“长期存在的”,并且它需要保持对许多不同事物的引用,所以matplotlib对大多数用户提供的对象使用“弱”引用。这样就可以对事物进行垃圾收集,即使数字仍然可以引用它们。

在大多数情况下使用它是有充分理由的,但你可以提出一个论点,即matplotlib不应该使用弱引用来进行回调。无论如何,您所经历的是这个长期存在的功能/错误/设计选择的结果。


最简单的解决方案就是:

x = ZoomingSubplots(fig)

x的引用将使类实例不被垃圾收集,直到x超出范围(并且因为x在这种特殊情况下是一个全局变量,它赢了'直到程序结束才发生。)


上面的解决方案完美无缺,但我不喜欢闲置未使用的变量(像pylint等等,也会抱怨)。

因此,我经常会在此类事件中添加show方法,调用plt.show()并进入gui主循环。例如:

import numpy as np
from matplotlib import pyplot as plt

class ZoomingSubplots(object):
    def __init__(self, fig):
        print 'class entered'
        self.fig = fig
        self.fig.canvas.mpl_connect('button_press_event', self.on_click)

    def on_click(self, event):
        print 'click event'

    def show(self):
        plt.show()

#make a figure with 9 random imshows
fig = plt.figure()
for i in range(9):
    arr = np.random.rand(10,10)
    ax = fig.add_subplot(3, 3, i+1)
    ax.imshow(arr, interpolation = 'nearest')

# connect with zoom class
ZoomingSubplots(fig).show()

在这种情况下,gui mainloop(即plt.show())被称为类实例的方法,因此,在show()结束之前,不能对类实例进行垃圾回收。

哪个“更好”纯粹是个人风格的问题。但是,我觉得第一个例子不太适合未来,因为其他人可能会出现并且“x未使用,让我们删除它”,并无意中重新引入问题。