pythonpython-2.7matplotlibmatplotlib-widget

RectangleSelector Disappears on Zoom


When I run this example and create a rectangular selection if I zoom or move the plot window around the selection disappears until I deselect the move or zoom tool and click on the plot window again.

I am using %matplotlib tkinter in an IPython notebook.

I've attempted hooking into the limit changes that occur when the window is zoomed and setting the rectangular selection to visible:

def persist_rect(newlims):
    rs = toggle_selector.RS
    print(rs.visible)
    rs.set_visible(True)
    rs.update()

current_ax.callbacks.connect('xlim_changed', persist_rect)
current_ax.callbacks.connect('ylim_changed', persist_rect)

But this doesn't seem to do anything. It doesn't even appear that toggle_selector.RS.visible is ever set to false.

I've also been looking at the source for RectangleSelector, but I haven't seen anything enlightening there.

I've also discovered that I have this issue when I modify the extent of the selected region using RectangleSelector.extents = new_extents. When .extents is modified, for example with a slider widget, the selected region disappears until I click on the plot again.

All of these problems problems go away if the RectangleSelector is initialized with useblit=False as @ImportanceOfBeingErnest suggests, but, as they say, it's not a very performant solution.


Solution

  • Adding a callback for draw_events:

    def mycallback(event):
        if RS.active:
            RS.update()
    plt.connect('draw_event', mycallback)
    

    makes the RectangleSelector persist after zooming or panning, and is compatible with useblit=True.


    For example, using the code from the docs as a base:

    from __future__ import print_function
    from matplotlib.widgets import RectangleSelector
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.widgets as widgets
    import threading
    import datetime as DT
    
    def line_select_callback(eclick, erelease):
        'eclick and erelease are the press and release events'
        x1, y1 = eclick.xdata, eclick.ydata
        x2, y2 = erelease.xdata, erelease.ydata
        print("(%3.2f, %3.2f) --> (%3.2f, %3.2f)" % (x1, y1, x2, y2))
        print(" The button you used were: %s %s" % (eclick.button, erelease.button))
    
    def toggle_selector(event):
        print(' Key pressed: {}'.format(event.key))
        if event.key in ['D', 'd'] and RS.active:
            print(' RectangleSelector deactivated.')
            RS.set_active(False)
            RS.set_visible(False)
            RS.update()
        if event.key in ['A', 'a'] and not RS.active:
            print(' RectangleSelector activated.')
            RS.set_active(True)
            RS.set_visible(True)
            RS.update()
    
    def mycallback(event):
        if RS.active:
            # print('mycallback')
            RS.update()
    
    # def persist_rect(newlims):
    #     print('persist_rect')
    #     RS.set_visible(True)
    #     RS.update()
    
    fig, ax = plt.subplots() 
    # figtype = type(fig)
    # figtype._draw = figtype.draw
    # def mydraw(self, renderer):
    #     print('figure.draw')
    #     self._draw(renderer)
    # figtype.draw = mydraw
    
    N = 100000               
    x = np.linspace(0.0, 10.0, N) 
    
    RS = RectangleSelector(ax, line_select_callback,
                           drawtype='box', useblit=True,
                           button=[1, 3],  # don't use middle button
                           minspanx=5, minspany=5,
                           spancoords='pixels',
                           interactive=True)
    
    plt.plot(x, +np.sin(.2*np.pi*x), lw=3.5, c='b', alpha=.7) 
    plt.plot(x, +np.cos(.2*np.pi*x), lw=3.5, c='r', alpha=.5)
    plt.plot(x, -np.sin(.2*np.pi*x), lw=3.5, c='g', alpha=.3)
    
    plt.connect('key_press_event', toggle_selector)
    plt.connect('draw_event', mycallback)
    # ax.callbacks.connect('xlim_changed', persist_rect)
    # ax.callbacks.connect('ylim_changed', persist_rect)
    
    plt.show()
    

    Why does mycallback work but persist_rect doesn't?

    If you uncomment the commented-out statements above, you'll get some printed output which will look something as this:

    figure.draw
    mycallback
    figure.draw
    mycallback
    (4.09, -0.53) --> (8.15, 0.38)
     The button you used were: 1 1
    persist_rect
    persist_rect
    figure.draw
    mycallback
     Key pressed: q
    

    Notice that persist_rect gets called before figure.draw, while mycallback is called afterwards. figure.draw does not draw the RectangleSelection, but it does draw the Rectangle used for the background. So figure.draw obscures RectangleSelection. Thus persist_rect momentarily displays RectangleSelection, but it fails to persist. mycallback works because it is called after figure.draw.