我有一个包含多个轴的图形,其中有几个我想要绘制N个矩形到高x间隔。每个面板上会有N个相同的矩形。单击一下,在所有面板上创建一个新的矩形,按下鼠标按钮并在鼠标移动时,矩形在光标位置后的所有面板上调整大小,在释放时我停止调整大小。新点击会在所有面板上创建一个新矩形等。
每次更新图形时,我都有一个涉及fig.canvas.draw()的工作版本。这对于按下和释放事件是可以的,因为它们很少,但对于motion_notify_event,在实际情况下这太慢了(14个绘图重绘)。
以下是该版本的一个小工作示例:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
class Zones(object):
"""
this class allows the user to draw rectangular zones
across several axes and get the corresponding intervals
"""
#==========================================================
#==========================================================
def __init__(self,axes, nbzones=4):
"""create an area made of 'nbzone' zone
@param nbzones : max number of rectangular zones to be drawn
@param axes : one or several axes in a list [ax1,]
Exemple : Zone(axes, nbzones=5)
Creation : 2013-11-13 09:08:45.239114
"""
self.axes = axes # the list of axes
self.fig = axes[0].figure # the (only) associated figure
# events : press, released, motion
self.pressid = self.fig.canvas.mpl_connect('button_press_event',
self._on_click)
self.releaid = self.fig.canvas.mpl_connect('button_release_event',
self._on_release)
self.motioid = self.fig.canvas.mpl_connect('motion_notify_event',
self._on_motion)
# allows to move the rectangle only when button is pressed
self._is_pressed = False
#will count the number of defined zones
self.count = 0
self.nbzones = nbzones # max number of defined zones
# will store the defined intervals
self.intervals = np.ndarray(shape=(nbzones,2))
#==========================================================
#==========================================================
#==========================================================
def _on_click(self,event):
"""on_click event
Creation : 2013-11-13 09:13:37.922407
"""
self._is_pressed = True # the button is now defined as pressed
# store the rectangles for all axes
# this will be useful because other events will loop on them
self.rects = []
# for now loop on all axes and create one set of identical rectangles
# on each one of them
# the width of the rect. is something tiny like 1e-5 to give the
# impression that it has 0 width before the user moves the mouse
for ax in self.axes:
self.rects.append(Rectangle((event.xdata,0),
1e-5, 10,
alpha=0.3,
color='g'))
# add the rectangle to the current ax
ax.add_patch(self.rects[-1])
# the set of rectangles is created for all axes
# now draw them
self.fig.canvas.draw()
#==========================================================
#==========================================================
#==========================================================
def _on_release(self,event):
"""on_release event
Creation : 2013-11-13 09:18:04.246367
"""
self._is_pressed = False # the button is now released
# now we loop on all the rects defined last click
# and we change their width according to the current
# position of the cursor and x0
for rect in self.rects:
rect.set_width(event.xdata - rect.get_x())
# all rects. have been updated so draw them
self.fig.canvas.draw()
# now store the interval and increment the
# total number of defined zones
self.intervals[self.count,0] = rect.get_x()
self.intervals[self.count,1] = event.xdata
self.count+=1
# if we have reached the max number of zones
# then we stop listening for events
if self.count == self.nbzones:
self.fig.canvas.mpl_disconnect(self.pressid)
self.fig.canvas.mpl_disconnect(self.releaid)
self.fig.canvas.mpl_disconnect(self.motioid)
print 'all intervals defined!'
#==========================================================
#==========================================================
#==========================================================
def _on_motion(self,event):
""" on_motion event
Creation : 2013-11-13 09:21:59.747476
"""
# this event must apply only when the mouse button is pressed
if self._is_pressed == True:
print 'motion while pressed'
# we loop over rectangles of this click
# and update their width according to the current
# position of the cursor
for rect in self.rects:
rect.set_width(event.xdata - rect.get_x())
# all rects. have been updated so draw them
self.fig.canvas.draw()
#==========================================================
#==========================================================
#==========================================================
def get_intervals(self):
""" returns an array of all the intervals (zones)
@return: ndarray shape = (nbzones,2)
Exemple : intervals = myzones.get_intervals()
Creation : 2013-11-13 09:23:44.147194
"""
self.intervals.sort() # we want x0 < x1
return self.intervals
#==========================================================
def main():
fig = plt.figure()
ax1 = fig.add_subplot(411)
ax2 = fig.add_subplot(412)
ax3 = fig.add_subplot(413)
ax4 = fig.add_subplot(414)
axes = [ax1,ax2,ax3,ax4]
for ax in axes:
ax.set_xlim((0,10))
ax.set_ylim((0,10))
z = Zones(axes, nbzones=6)
return z
if __name__ == '__main__':
main()
我想编写一个版本,在此动作事件中,在每个面板上重绘,只显示当前正在调整大小的矩形。
我不确定我是否完全理解如何使用canvas.blit(),axes.draw_artist()。我想我需要关注'_on_motion()'方法。
这是我的尝试,我已经改变了'_on_motion()'方法:
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import numpy as np
class Zones(object):
"""
this class allows the user to draw rectangular zones
across several axes and get the corresponding intervals
"""
#==========================================================
#==========================================================
def __init__(self,axes, nbzones=4):
"""create an area made of 'nbzone' zone
@param nbzones : max number of rectangular zones to be drawn
@param axes : one or several axes in a list [ax1,]
Exemple : Zone(axes, nbzones=5)
Creation : 2013-11-13 09:08:45.239114
"""
self.axes = axes # the list of axes
self.fig = axes[0].figure # the (only) associated figure
# events : press, released, motion
self.pressid = self.fig.canvas.mpl_connect('button_press_event',
self._on_click)
self.releaid = self.fig.canvas.mpl_connect('button_release_event',
self._on_release)
self.motioid = self.fig.canvas.mpl_connect('motion_notify_event',
self._on_motion)
# allows to move the rectangle only when button is pressed
self._is_pressed = False
#will count the number of defined zones
self.count = 0
self.nbzones = nbzones # max number of defined zones
# will store the defined intervals
self.intervals = np.ndarray(shape=(nbzones,2))
#==========================================================
#==========================================================
#==========================================================
def _on_click(self,event):
"""on_click event
Creation : 2013-11-13 09:13:37.922407
"""
self._is_pressed = True # the button is now defined as pressed
# store the rectangles for all axes
# this will be useful because other events will loop on them
self.rects = []
# for now loop on all axes and create one set of identical rectangles
# on each one of them
# the width of the rect. is something tiny like 1e-5 to give the
# impression that it has 0 width before the user moves the mouse
for ax in self.axes:
self.rects.append(Rectangle((event.xdata,0),
1e-5, 10,
alpha=0.3,
color='g'))
# add the rectangle to the current ax
ax.add_patch(self.rects[-1])
# the set of rectangles is created for all axes
# now draw them
self.fig.canvas.draw()
#==========================================================
#==========================================================
#==========================================================
def _on_release(self,event):
"""on_release event
Creation : 2013-11-13 09:18:04.246367
"""
self._is_pressed = False # the button is now released
# now we loop on all the rects defined last click
# and we change their width according to the current
# position of the cursor and x0
for rect in self.rects:
rect.set_width(event.xdata - rect.get_x())
# all rects. have been updated so draw them
self.fig.canvas.draw()
# now store the interval and increment the
# total number of defined zones
self.intervals[self.count,0] = rect.get_x()
self.intervals[self.count,1] = event.xdata
self.count+=1
# if we have reached the max number of zones
# then we stop listening to events
if self.count == self.nbzones:
self.fig.canvas.mpl_disconnect(self.pressid)
self.fig.canvas.mpl_disconnect(self.releaid)
self.fig.canvas.mpl_disconnect(self.motioid)
print 'all intervals defined!'
#==========================================================
#==========================================================
#==========================================================
def _on_motion(self,event):
""" on_motion event
Creation : 2013-11-13 09:21:59.747476
"""
# this event must apply only when the mouse button is pressed
if self._is_pressed == True:
print 'motion while pressed'
# we loop over rectangles of this click
# and update their width according to the current
# position of the cursor
for ax,rect in zip(self.axes,self.rects):
rect.set_width(event.xdata - rect.get_x())
ax.draw_artist(rect)
self.fig.canvas.blit(rect.clipbox)
#==========================================================
#==========================================================
#==========================================================
def get_intervals(self):
""" returns an array of all the intervals (zones)
@return: ndarray shape = (nbzones,2)
Exemple : intervals = myzones.get_intervals()
Creation : 2013-11-13 09:23:44.147194
"""
self.intervals.sort() # we want x0 < x1
return self.intervals
#==========================================================
def main():
fig = plt.figure()
ax1 = fig.add_subplot(411)
ax2 = fig.add_subplot(412)
ax3 = fig.add_subplot(413)
ax4 = fig.add_subplot(414)
axes = [ax1,ax2,ax3,ax4]
for ax in axes:
ax.set_xlim((0,10))
ax.set_ylim((0,10))
z = Zones(axes, nbzones=6)
return z
if __name__ == '__main__':
main()
在我的机器上,当按下按钮时移动光标时,我看到只在底部面板上绘制的矩形,看起来很奇怪,就像有几个矩形被绘制,我不知道......但是没有前三个轴。释放鼠标按钮后,我会看到所有面板上的所有矩形都以适当的大小绘制。
我已经制作了一个更简单的示例代码,看看draw_artist()和blit()发生了什么,这显然做了我认为应该做的事情:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
def rect():
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_subplot(212)
axes = [ax1,ax2]
rects = []
for ax in axes:
rects.append(mpatches.Rectangle([0,0],0.2,1,color='b',alpha=0.4))
ax.add_artist(rects[-1])
fig.canvas.draw()
# now resize
for ax,r in zip(axes,rects):
r.set_width(0.1)
r.set_color('r')
r.set_alpha(0.2)
ax.draw_artist(r)
fig.canvas.blit(r.clipbox)
# fig.canvas.blit(ax.bbox) # this seems to do the same job as r.clipbox??
即。在两个轴上创建并绘制具有给定宽度的矩形,然后更改所有轴的矩形属性(这模拟鼠标运动)并重新绘制矩形。这似乎有效,因为我不再看到蓝色矩形而只看到红色矩形。
我没有得到的是这个小代码显然和我的_on_motion()方法做的一样,那有什么不对?