在鼠标移动时调整多个轴上的矩形:使用blit,draw_artist

时间:2013-11-18 11:29:06

标签: python matplotlib event-handling

我有一个包含多个轴的图形,其中有几个我想要绘制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()方法做的一样,那有什么不对?

0 个答案:

没有答案