为啥代码片段在 matplotlib 2.0.2 上运行良好,但在 matplotlib 2.1.0 上引发错误
Posted
技术标签:
【中文标题】为啥代码片段在 matplotlib 2.0.2 上运行良好,但在 matplotlib 2.1.0 上引发错误【英文标题】:Why a code snippet works fine with matplotlib 2.0.2 but raise an error with matplotlib 2.1.0为什么代码片段在 matplotlib 2.0.2 上运行良好,但在 matplotlib 2.1.0 上引发错误 【发布时间】:2017-11-22 15:49:42 【问题描述】:我有以下代码可以很好地与 matplotlib 2.0.2(在 Python 3.6.3 下)一起使用,但会引发类型错误(TypeError: 'Rectangle' object不可调用)与 matplotlib 2.1.0。 它的目的是通过“press&move”在 PyQt5 窗口中嵌入的图形上交互地绘制一个矩形,当用户释放鼠标按钮 1 时,矩形给出了图形的新限制(即像 Matlab 一样的缩放行为)。
我做错了什么?
# Python 3.6
...
from PyQt5 import QtCore, QtGui, QtWidgets
...
from matplotlib.patches import Rectangle
...
class MplWidget(Canvas):
zoom_in = False
cid_zoom_P = None
cid_zoom_R = None
cid_zoom_M = None
def __init__(self, parent, dpi = 100, hold = False, **kwargs):
super().__init__(Figure())
self.parent = parent
self.setParent(parent)
self.figure = Figure(dpi = dpi)
self.canvas = Canvas(self.figure)
self.repres = self.figure.add_subplot(111)
...
def zoomManager(self, bouton_pan, bouton_tip):
if self.zoom_in is not True:
self.zoom_in = True
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))
self.x0 = None
self.y0 = None
self.x1 = None
self.y1 = None
self.cid_zoom_P = self.mpl_connect("button_press_event", self.zoomOnPress)
self.cid_zoom_R = self.mpl_connect("button_release_event", self.zoomOnRelease)
self.cid_zoom_M = self.mpl_connect("motion_notify_event", self.zoomOnMotion)
else:
self.zoom_in = False
QtWidgets.QApplication.restoreOverrideCursor()
self.mpl_disconnect(self.cid_zoom_P)
self.mpl_disconnect(self.cid_zoom_R)
self.mpl_disconnect(self.cid_zoom_M)
...
def zoomOnPress(self, event):
if event.button == 1:
if not bool(event.inaxes):
return
self.zoom_pressed = True
self.x0 = event.xdata
self.y0 = event.ydata
self.rect = Rectangle((0,0), 1, 1, alpha=0.25, ls='--', lw=1, ec='k')
self.repres.add_patch(self.rect)
def zoomOnRelease(self, event):
if event.button == 1:
self.zoom_pressed = False
self.x1 = event.xdata
if not self.x1:
self.x1 = self.x1_old
else:
self.x1_old = self.x1
self.y1 = event.ydata
if not self.y1:
self.y1 = self.y1_old
else:
self.y1_old = self.y1
self.rect.remove()
self.repres.set_xlim([min(self.x1, self.x0), max(self.x1, self.x0)])
self.repres.set_ylim([min(self.y1, self.y0), max(self.y1, self.y0)])
self.draw()
def zoomOnMotion(self, event):
if self.zoom_pressed is False:
return
self.x1 = event.xdata
if not self.x1:
self.x1 = self.x1_old
else:
self.x1_old = self.x1
self.y1 = event.ydata
if not self.y1:
self.y1 = self.y1_old
else:
self.y1_old = self.y1
self.rect.set_width(self.x1 - self.x0)
self.rect.set_height(self.y1 - self.y0)
self.rect.set_xy((self.x0, self.y0))
self.draw()
...
【问题讨论】:
您需要提供问题的minimal reproducible example,以便人们能够重现它并找到错误。如果没有minimal reproducible example,这个问题就很难解决。您还需要提供完整的错误回溯,而不仅仅是最后一行。 @ImportanceOfBeingErnest :这是一个关于 matplotlib 版本和向后兼容性的理论问题。真正的代码太长太复杂,无法在此处发布。我已经花了很多时间写这个问题...... 那么理论上的答案是:不,Rectangle
的功能在两个版本之间没有变化,并且 Rectangle 是向后兼容的。如果您仍然对实际问题的解决方案感兴趣,请参见上文。
@ImportanceOfBeingErnest :我对您的回答感到惊讶,因为在我的情况下,这两个版本的 Matplotlib 之间的操作存在真正的差异。此处部分公开的代码在 Matplotlib 2.0.2 中完美运行,但不适用于 2.1.0 版本。在最后一种情况下,错误是对“self.draw”的调用产生的。我已经测试了这个语句的很多变体,但没有成功。我将尝试在“PyPlot”下重现错误,为您提供所需的示例功能和可重现性。感谢您的帮助
不要误会我的意思,我并不是说版本之间没有任何会导致此错误的更改。我要说的是Rectangle
没有变化。为了找出导致错误的原因,需要一个完整且可验证的示例,充其量也应该是最小的。只有这样才能开始调试。
【参考方案1】:
以下代码可以完美地与 2.0.2 和 2.1.0 matplotlib 版本一起使用。 但我认为它不能代表我遇到的问题。无论如何我都会给出它,因为这对于那些对所涉及的机制感兴趣的人来说是一个例子(我在网上找不到类似的例子)。
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
xdata = np.linspace(0,9*np.pi, num=301)
ydata = np.sin(xdata)
fig, ax = plt.subplots()
line, = ax.plot(xdata, ydata)
class Zoom(object):
zoom_in = False
def __init__(self, fig, ax):
self.fig = fig
self.ax = ax
self.x0 = None
self.y0 = None
self.x1 = None
self.y1 = None
self.ax.figure.canvas.mpl_connect('button_press_event', self.on_press)
self.ax.figure.canvas.mpl_connect('button_release_event', self.on_release)
self.ax.figure.canvas.mpl_connect("motion_notify_event", self.on_motion)
def on_press(self, event):
print('press')
if event.button == 1:
if not bool(event.inaxes):
return
self.x0 = event.xdata
self.y0 = event.ydata
self.rect = Rectangle((0,0), 1, 1, alpha=0.25, ls='--', lw=1, ec='k')
self.ax.add_patch(self.rect)
self.zoom_in = True
def on_release(self, event):
print('release')
if event.button == 1:
self.x1 = event.xdata
if not self.x1: # user is out of axis
self.x1 = self.x1_old
else: # user is over axis
self.x1_old = self.x1
self.y1 = event.ydata
if not self.y1:
self.y1 = self.y1_old
else:
self.y1_old = self.y1
self.rect.remove()
self.ax.set_xlim([min(self.x1, self.x0), max(self.x1, self.x0)])
self.ax.set_ylim([min(self.y1, self.y0), max(self.y1, self.y0)])
self.ax.figure.canvas.draw()
def on_motion(self, event):
if self.zoom_in is True:
self.x1 = event.xdata
if not self.x1:
self.x1 = self.x1_old
else:
self.x1_old = self.x1
self.y1 = event.ydata
if not self.y1:
self.y1 = self.y1_old
else:
self.y1_old = self.y1
self.rect.set_width(self.x1 - self.x0)
self.rect.set_height(self.y1 - self.y0)
self.rect.set_xy((self.x0, self.y0))
#self.draw() # Statement that is problematic
#plt.draw() # Its equivalent under 'Pyplot'
self.ax.figure.canvas.draw()
a = Zoom(fig, ax)
plt.show()
在我研究的这一点上,我认为问题不在于补丁“矩形”,而在于以下行,其中“self”是 PyQt5 的“matplotlib 小部件”:
self.cid_zoom_P = self.mpl_connect("button_press_event", self.zoomOnPress)
self.cid_zoom_R = self.mpl_connect("button_release_event", self.zoomOnRelease)
self.cid_zoom_M = self.mpl_connect("motion_notify_event", self.zoomOnMotion)
无论如何,我将在这个问题上多花一点时间。如果我找不到我做错了什么,我会将 Matplotlib 降级到 2.0.2。
【讨论】:
【参考方案2】:我找到了解决这个问题的方法,现在,我可以使用 PyQt5 和 Matplotlib 2.1.2 进行缩放(我在 Matplotlib 2.2.2 中遇到了另一个问题,tkagg 不再有 cursord 属性)。
这是一个名为 backend_qt5agg.py 的文件,它引发了我的错误(矩形不可调用)。 第 89 行和下一个,有:
if self._bbox_queue:
bbox_queue = self._bbox_queue
else:
painter.eraseRect(self.rect()) # this is where a Rectangle is 'called'
bbox_queue = [
Bbox([[0, 0], [self.renderer.width, self.renderer.height]])]
我刚刚添加了一个像这样的“try/except”块:
if self._bbox_queue:
bbox_queue = self._bbox_queue
else:
try:
painter.eraseRect(self.rect())
except:
pass
bbox_queue = [
Bbox([[0, 0], [self.renderer.width, self.renderer.height]])]
而且效果很好。
【讨论】:
以上是关于为啥代码片段在 matplotlib 2.0.2 上运行良好,但在 matplotlib 2.1.0 上引发错误的主要内容,如果未能解决你的问题,请参考以下文章
为啥这个混淆矩阵(matplotlib)在 Jupyter Notebook 中看起来被压扁了? [复制]