11

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.

alessandro
  • 363
  • 1
  • 12
  • Have you check this questions answers: https://stackoverflow.com/questions/34517484/persistent-rectangle-selector? – Jaffer Wilson Aug 16 '17 at 11:48
  • @JafferWilson: Thanks for pointing to that question. Unfortunately the answer is not working. When being used as it is, the Rectangle will instantly disappear. If being used with `interactive=True`, the same issue as in the above question remains. – ImportanceOfBeingErnest Aug 16 '17 at 13:05

3 Answers3

6

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.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Indeed! Is there any good explanation for that? In principle this is very close to what OP tried with the callback on the limits change (and which doesn't work). Also it seems unintuitive to call `.update`, which would actually draw the rectangle, *on a callback* of a draw event. Feels a bit like the snake bites its own tail, but it's obviously working. – ImportanceOfBeingErnest Aug 16 '17 at 23:24
  • 1
    I think it boils down to the order in which the callbacks are called relative to `figure.draw`. `persist_rect` gets called before `figure.draw`, while `mycallback` gets called afterwards. Since `figure.draw` draws on top of the `RectangleSelector`, `persist_rect` fail to keep the `RectangleSelector` persistently visible. I've added some monkey-patching code above to show when `figure.draw` gets called. – unutbu Aug 17 '17 at 02:06
5

If I understand correctly, the rectangle selector should stay visible throughout the process of panning or zooming. This could be achieved by not using blitting,

toggle_selector.RS = RectangleSelector(ax, ...,  useblit=False, ...)

A side effect of this is that the plotting may become slow depending on the complexity of the plot, as without blitting, the complete plot is continuously redrawn while using the rectangle selector.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • 1
    Thanks for your help, unfortunately this isn't an optimal solution for me. I wanted to use the rectangle selector to select regions on waterfall plots drawn with `imshow`. The smallest of these waterfall arrays are ~18MB and the largest are around ~220MB. `RectangleSelector` works wonderfully with blitting except for this redrawing problem. Is there any way I can manually force it to redraw after a zoom/move? – alessandro Aug 07 '17 at 01:30
  • 1
    This works wonderfully for simple plots. I'll happily +1 when I have enough rep. – alessandro Aug 07 '17 at 01:33
0

In the source code for RectangularSelector the release method (line 2119) handles the visibility of the selector

def _release(self, event):   
"""on button release event"""
    if not self.interactive:
        self.to_draw.set_visible(False)

Subclass RectangleSelector to modify the release method

class visibleRectangleSelector(RectangleSelector):
    def release(self, event):
        super(visibleRectangleSelector, self).release(event)
        self.to_draw.set_visible(True)
        self.canvas.draw()   ##updates canvas for new selection

Sample Code using doc example

from __future__ import print_function
"""
Do a mouseclick somewhere, move the mouse to some destination, release
the button.  This class gives click- and release-events and also draws
a line or a box from the click-point to the actual mouseposition
(within the same axes) until the button is released.  Within the
method 'self.ignore()' it is checked whether the button from eventpress
and eventrelease are the same.

"""
from matplotlib.widgets import RectangleSelector
import numpy as np
import matplotlib.pyplot as plt


class visibleRectangleSelector(RectangleSelector):
    def release(self, event):
        super(visibleRectangleSelector, self).release(event)
        self.to_draw.set_visible(True)
        self.canvas.draw() 


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.')
    if event.key in ['Q', 'q'] and toggle_selector.RS.active:
        print(' RectangleSelector deactivated.')
        toggle_selector.RS.set_active(False)
    if event.key in ['A', 'a'] and not toggle_selector.RS.active:
        print(' RectangleSelector activated.')
        toggle_selector.RS.set_active(True)


fig, current_ax = plt.subplots()  # make a new plotting range
N = 100000  # If N is large one can see
x = np.linspace(0.0, 10.0, N)  # improvement by use blitting!

plt.plot(x, +np.sin(.2 * np.pi * x), lw=3.5, c='b', alpha=.7)  # plot something
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)

print("\n      click  -->  release")

# drawtype is 'box' or 'line' or 'none'
toggle_selector.RS = RectangleSelector(
    current_ax,
    line_select_callback,
    drawtype='box',
    useblit=False,
    button=[1, 3],  # don't use middle button
    minspanx=5,
    minspany=5,
    spancoords='pixels',
    interactive=True)
plt.connect('key_press_event', toggle_selector)
plt.show()
Nithin Varghese
  • 893
  • 1
  • 6
  • 28
  • 1
    Thanks for looking into this issue. However, it seems that this solution is only working because you used `blit=False` as in [my answer](https://stackoverflow.com/a/45531329/4124317). When using `blit=False`, no subclassing is needed at all. When trying this solution with `blit=True`, the RectangleSelector still disappears. So it does not seem to solve the issue. – ImportanceOfBeingErnest Aug 16 '17 at 07:41
  • Seen from @JafferWilson's comment above, this answer is a **plagiate** of the answer to [this question](https://stackoverflow.com/questions/34517484/persistent-rectangle-selector). – ImportanceOfBeingErnest Aug 16 '17 at 13:14