class QuadraticSelector: """ Select a min/max range of the x axes for a matplotlib Axes Modified by Helge Pettersen to make quadratic boxes as onscreen / output Example usage:: from matplotlib.widgets import RectangleSelector from pylab import * def onselect(eclick, erelease): 'eclick and erelease are matplotlib events at press and release' print ' startposition : (%f, %f)' % (eclick.xdata, eclick.ydata) print ' endposition : (%f, %f)' % (erelease.xdata, erelease.ydata) print ' used button : ', eclick.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) x = arange(100)/(99.0) y = sin(x) fig = figure ax = subplot(111) ax.plot(x,y) toggle_selector.RS = RectangleSelector(ax, onselect, drawtype='line') connect('key_press_event', toggle_selector) show() """ def __init__(self, ax, onselect, drawtype='box', minspanx=None, minspany=None, useblit=False, lineprops=None, rectprops=None, spancoords='data', button=None): """ Create a selector in *ax*. When a selection is made, clear the span and call onselect with:: onselect(pos_1, pos_2) and clear the drawn box/line. The ``pos_1`` and ``pos_2`` are arrays of length 2 containing the x- and y-coordinate. If *minspanx* is not *None* then events smaller than *minspanx* in x direction are ignored (it's the same for y). The rectangle is drawn with *rectprops*; default:: rectprops = dict(facecolor='red', edgecolor = 'black', alpha=0.5, fill=False) The line is drawn with *lineprops*; default:: lineprops = dict(color='black', linestyle='-', linewidth = 2, alpha=0.5) Use *drawtype* if you want the mouse to draw a line, a box or nothing between click and actual position by setting ``drawtype = 'line'``, ``drawtype='box'`` or ``drawtype = 'none'``. *spancoords* is one of 'data' or 'pixels'. If 'data', *minspanx* and *minspanx* will be interpreted in the same coordinates as the x and y axis. If 'pixels', they are in pixels. *button* is a list of integers indicating which mouse buttons should be used for rectangle selection. You can also specify a single integer if only a single button is desired. Default is *None*, which does not limit which button can be used. Note, typically: 1 = left mouse button 2 = center mouse button (scroll wheel) 3 = right mouse button """ self.ax = ax self.visible = True self.canvas = ax.figure.canvas self.canvas.mpl_connect('motion_notify_event', self.onmove) self.canvas.mpl_connect('button_press_event', self.press) self.canvas.mpl_connect('button_release_event', self.release) self.canvas.mpl_connect('draw_event', self.update_background) self.active = True # for activation / deactivation self.to_draw = None self.background = None if drawtype == 'none': drawtype = 'line' # draw a line but make it self.visible = False # invisible if drawtype == 'box': if rectprops is None: rectprops = dict(facecolor='white', edgecolor='black', alpha=0.5, fill=False) self.rectprops = rectprops self.to_draw = Rectangle((0, 0), 0, 1, visible=False, **self.rectprops) self.ax.add_patch(self.to_draw) if drawtype == 'line': if lineprops is None: lineprops = dict(color='black', linestyle='-', linewidth=2, alpha=0.5) self.lineprops = lineprops self.to_draw = Line2D([0, 0], [0, 0], visible=False, **self.lineprops) self.ax.add_line(self.to_draw) self.onselect = onselect self.useblit = useblit self.minspanx = minspanx self.minspany = minspany if button is None or isinstance(button, list): self.validButtons = button elif isinstance(button, int): self.validButtons = [button] assert (spancoords in ('data', 'pixels')) self.spancoords = spancoords self.drawtype = drawtype # will save the data (position at mouseclick) self.eventpress = None # will save the data (pos. at mouserelease) self.eventrelease = None def update_background(self, event): 'force an update of the background' if self.useblit: self.background = self.canvas.copy_from_bbox(self.ax.bbox) def ignore(self, event): 'return *True* if *event* should be ignored' # If RectangleSelector is not active : if not self.active: return True # If canvas was locked if not self.canvas.widgetlock.available(self): return True # Only do rectangle selection if event was triggered # with a desired button if self.validButtons is not None: if not event.button in self.validButtons: return True # If no button was pressed yet ignore the event if it was out # of the axes if self.eventpress == None: return event.inaxes != self.ax # If a button was pressed, check if the release-button is the # same. return (event.inaxes != self.ax or event.button != self.eventpress.button) def press(self, event): 'on button press event' # Is the correct button pressed within the correct axes? if self.ignore(event): return # make the drawed box/line visible get the click-coordinates, # button, ... self.to_draw.set_visible(self.visible) self.eventpress = event return False def release(self, event): 'on button release event' if self.eventpress is None or self.ignore(event): return # make the box/line invisible again self.to_draw.set_visible(False) self.canvas.draw() # release coordinates, button, ... self.eventrelease = event if self.spancoords == 'data': xmin, ymin = self.eventpress.xdata, self.eventpress.ydata xmax, ymax = self.eventrelease.xdata, self.eventrelease.ydata # calculate dimensions of box or line get values in the right # order elif self.spancoords == 'pixels': xmin, ymin = self.eventpress.x, self.eventpress.y xmax, ymax = self.eventrelease.x, self.eventrelease.y else: raise ValueError('spancoords must be "data" or "pixels"') if xmin > xmax: xmin, xmax = xmax, xmin if ymin > ymax: ymin, ymax = ymax, ymin spanx = xmax - xmin spany = ymax - ymin xproblems = self.minspanx is not None and spanx < self.minspanx yproblems = self.minspany is not None and spany < self.minspany # TODO: Why is there triple-quoted items, and two separate checks. if (self.drawtype == 'box') and (xproblems or yproblems): """Box to small""" # check if drawn distance (if it exists) is return # not too small in neither x nor y-direction if (self.drawtype == 'line') and (xproblems and yproblems): """Line to small""" # check if drawn distance (if it exists) is return # not too small in neither x nor y-direction self.onselect(self.eventpress, self.eventrelease) # call desired function self.eventpress = None # reset the variables to their self.eventrelease = None # inital values return False def update(self): 'draw using newfangled blit or oldfangled draw depending on useblit' if self.useblit: if self.background is not None: self.canvas.restore_region(self.background) self.ax.draw_artist(self.to_draw) self.canvas.blit(self.ax.bbox) else: self.canvas.draw_idle() return False def onmove(self, event): 'on motion notify event if box/line is wanted' if self.eventpress is None or self.ignore(event): return x, y = event.xdata, event.ydata # actual position (with # (button still pressed) if self.drawtype == 'box': minx, maxx = self.eventpress.xdata, x # click-x and actual mouse-x miny, maxy = self.eventpress.ydata, y # click-y and actual mouse-y if minx > maxx: minx, maxx = maxx, minx # get them in the right order if miny > maxy: miny, maxy = maxy, miny height = int(min(maxx - minx, maxy - miny)) self.to_draw.set_x(minx) # set lower left of box self.to_draw.set_y(miny) self.to_draw.set_width(height) # set width and height of box self.to_draw.set_height(height) self.update() self.quadminx = int(minx) self.quadminy = int(miny) self.quadmaxx = int(minx) + height self.quadmaxy = int(miny) + height return False if self.drawtype == 'line': self.to_draw.set_data([self.eventpress.xdata, x], [self.eventpress.ydata, y]) self.update() return False def set_active(self, active): """ Use this to activate / deactivate the RectangleSelector from your program with an boolean parameter *active*. """ self.active = active def get_active(self): """ Get status of active mode (boolean variable)""" return self.active def getQuad(self): """Returns (lower left (x,y), upper right (x,y).""" return ((int(self.quadminx), int(self.quadmaxx)), (int(self.quadminy), int(self.quadmaxy)))