class RectangleTool(CanvasToolBase, RectangleSelector): """Widget for selecting a rectangular region in a plot. After making the desired selection, press "Enter" to accept the selection and call the `on_enter` callback function. Parameters ---------- viewer : :class:`skimage.viewer.Viewer` Skimage viewer object. on_move : function Function called whenever a control handle is moved. This function must accept the rectangle extents as the only argument. on_release : function Function called whenever the control handle is released. on_enter : function Function called whenever the "enter" key is pressed. maxdist : float Maximum pixel distance allowed when selecting control handle. rect_props : dict Properties for :class:`matplotlib.patches.Rectangle`. This class redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. Attributes ---------- extents : tuple Rectangle extents: (xmin, xmax, ymin, ymax). """ def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, maxdist=10, rect_props=None): CanvasToolBase.__init__(self, viewer, on_move=on_move, on_enter=on_enter, on_release=on_release) props = dict(edgecolor=None, facecolor='r', alpha=0.15) props.update(rect_props if rect_props is not None else {}) if props['edgecolor'] is None: props['edgecolor'] = props['facecolor'] RectangleSelector.__init__(self, self.ax, lambda *args: None, rectprops=props) self.disconnect_events() # events are handled by the viewer # Alias rectangle attribute, which is initialized in RectangleSelector. self._rect = self.to_draw self._rect.set_animated(True) self.maxdist = maxdist self.active_handle = None self._extents_on_press = None if on_enter is None: def on_enter(extents): print("(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents) self.callback_on_enter = on_enter props = dict(mec=props['edgecolor']) self._corner_order = ['NW', 'NE', 'SE', 'SW'] xc, yc = self.corners self._corner_handles = ToolHandles(self.ax, xc, yc, marker_props=props) self._edge_order = ['W', 'N', 'E', 'S'] xe, ye = self.edge_centers self._edge_handles = ToolHandles(self.ax, xe, ye, marker='s', marker_props=props) self.artists = [ self._rect, self._corner_handles.artist, self._edge_handles.artist ] viewer.add_tool(self) @property def _rect_bbox(self): x0 = self._rect.get_x() y0 = self._rect.get_y() width = self._rect.get_width() height = self._rect.get_height() return x0, y0, width, height @property def corners(self): """Corners of rectangle from lower left, moving clockwise.""" x0, y0, width, height = self._rect_bbox xc = x0, x0 + width, x0 + width, x0 yc = y0, y0, y0 + height, y0 + height return xc, yc @property def edge_centers(self): """Midpoint of rectangle edges from left, moving clockwise.""" x0, y0, width, height = self._rect_bbox w = width / 2. h = height / 2. xe = x0, x0 + w, x0 + width, x0 + w ye = y0 + h, y0, y0 + h, y0 + height return xe, ye @property def extents(self): """Return (xmin, xmax, ymin, ymax).""" x0, y0, width, height = self._rect_bbox xmin, xmax = sorted([x0, x0 + width]) ymin, ymax = sorted([y0, y0 + height]) return xmin, xmax, ymin, ymax @extents.setter def extents(self, extents): x1, x2, y1, y2 = extents xmin, xmax = sorted([x1, x2]) ymin, ymax = sorted([y1, y2]) # Update displayed rectangle self._rect.set_x(xmin) self._rect.set_y(ymin) self._rect.set_width(xmax - xmin) self._rect.set_height(ymax - ymin) # Update displayed handles self._corner_handles.set_data(*self.corners) self._edge_handles.set_data(*self.edge_centers) self.set_visible(True) self.redraw() def on_mouse_release(self, event): if event.button != 1: return if not self.ax.in_axes(event): self.eventpress = None return RectangleSelector.release(self, event) self._extents_on_press = None # Undo hiding of rectangle and redraw. self.set_visible(True) self.redraw() self.callback_on_release(self.geometry) def on_mouse_press(self, event): if event.button != 1 or not self.ax.in_axes(event): return self._set_active_handle(event) if self.active_handle is None: # Clear previous rectangle before drawing new rectangle. self.set_visible(False) self.redraw() self.set_visible(True) RectangleSelector.press(self, event) def _set_active_handle(self, event): """Set active handle based on the location of the mouse event""" # Note: event.xdata/ydata in data coordinates, event.x/y in pixels c_idx, c_dist = self._corner_handles.closest(event.x, event.y) e_idx, e_dist = self._edge_handles.closest(event.x, event.y) # Set active handle as closest handle, if mouse click is close enough. if c_dist > self.maxdist and e_dist > self.maxdist: self.active_handle = None return elif c_dist < e_dist: self.active_handle = self._corner_order[c_idx] else: self.active_handle = self._edge_order[e_idx] # Save coordinates of rectangle at the start of handle movement. x1, x2, y1, y2 = self.extents # Switch variables so that only x2 and/or y2 are updated on move. if self.active_handle in ['W', 'SW', 'NW']: x1, x2 = x2, event.xdata if self.active_handle in ['N', 'NW', 'NE']: y1, y2 = y2, event.ydata self._extents_on_press = x1, x2, y1, y2 def on_move(self, event): if self.eventpress is None or not self.ax.in_axes(event): return if self.active_handle is None: # New rectangle x1 = self.eventpress.xdata y1 = self.eventpress.ydata x2, y2 = event.xdata, event.ydata else: x1, x2, y1, y2 = self._extents_on_press if self.active_handle in ['E', 'W'] + self._corner_order: x2 = event.xdata if self.active_handle in ['N', 'S'] + self._corner_order: y2 = event.ydata self.extents = (x1, x2, y1, y2) self.callback_on_move(self.geometry) @property def geometry(self): return self.extents
class RectangleTool(CanvasToolBase, RectangleSelector): """Widget for selecting a rectangular region in a plot. After making the desired selection, press "Enter" to accept the selection and call the `on_enter` callback function. Parameters ---------- viewer : :class:`skimage.viewer.Viewer` Skimage viewer object. on_move : function Function called whenever a control handle is moved. This function must accept the rectangle extents as the only argument. on_release : function Function called whenever the control handle is released. on_enter : function Function called whenever the "enter" key is pressed. maxdist : float Maximum pixel distance allowed when selecting control handle. rect_props : dict Properties for :class:`matplotlib.patches.Rectangle`. This class redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. Attributes ---------- extents : tuple Rectangle extents: (xmin, xmax, ymin, ymax). Examples ---------- >>> from skimage import data >>> from skimage.viewer import ImageViewer >>> from skimage.viewer.canvastools import RectangleTool >>> from skimage.draw import line >>> from skimage.draw import set_color >>> viewer = ImageViewer(data.coffee()) # doctest: +SKIP >>> def print_the_rect(extents): ... global viewer ... im = viewer.image ... coord = np.int64(extents) ... [rr1, cc1] = line(coord[2],coord[0],coord[2],coord[1]) ... [rr2, cc2] = line(coord[2],coord[1],coord[3],coord[1]) ... [rr3, cc3] = line(coord[3],coord[1],coord[3],coord[0]) ... [rr4, cc4] = line(coord[3],coord[0],coord[2],coord[0]) ... set_color(im, (rr1, cc1), [255, 255, 0]) ... set_color(im, (rr2, cc2), [0, 255, 255]) ... set_color(im, (rr3, cc3), [255, 0, 255]) ... set_color(im, (rr4, cc4), [0, 0, 0]) ... viewer.image=im >>> rect_tool = RectangleTool(viewer.ax, on_enter=print_the_rect) # doctest: +SKIP >>> viewer.show() # doctest: +SKIP """ def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, maxdist=10, rect_props=None): self._rect = None props = dict(edgecolor=None, facecolor='r', alpha=0.15) props.update(rect_props if rect_props is not None else {}) if props['edgecolor'] is None: props['edgecolor'] = props['facecolor'] RectangleSelector.__init__(self, viewer.ax, lambda *args: None, rectprops=props) CanvasToolBase.__init__(self, viewer, on_move=on_move, on_enter=on_enter, on_release=on_release) # Events are handled by the viewer try: self.disconnect_events() except AttributeError: # disconnect the events manually (hack for older mpl versions) [self.canvas.mpl_disconnect(i) for i in range(10)] # Alias rectangle attribute, which is initialized in RectangleSelector. self._rect = self.to_draw self._rect.set_animated(True) self.maxdist = maxdist self.active_handle = None self._extents_on_press = None if on_enter is None: def on_enter(extents): print("(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents) self.callback_on_enter = on_enter props = dict(mec=props['edgecolor']) self._corner_order = ['NW', 'NE', 'SE', 'SW'] xc, yc = self.corners self._corner_handles = ToolHandles(self.ax, xc, yc, marker_props=props) self._edge_order = ['W', 'N', 'E', 'S'] xe, ye = self.edge_centers self._edge_handles = ToolHandles(self.ax, xe, ye, marker='s', marker_props=props) self.artists = [self._rect, self._corner_handles.artist, self._edge_handles.artist] viewer.add_tool(self) @property def _rect_bbox(self): if not self._rect: return 0, 0, 0, 0 x0 = self._rect.get_x() y0 = self._rect.get_y() width = self._rect.get_width() height = self._rect.get_height() return x0, y0, width, height @property def corners(self): """Corners of rectangle from lower left, moving clockwise.""" x0, y0, width, height = self._rect_bbox xc = x0, x0 + width, x0 + width, x0 yc = y0, y0, y0 + height, y0 + height return xc, yc @property def edge_centers(self): """Midpoint of rectangle edges from left, moving clockwise.""" x0, y0, width, height = self._rect_bbox w = width / 2. h = height / 2. xe = x0, x0 + w, x0 + width, x0 + w ye = y0 + h, y0, y0 + h, y0 + height return xe, ye @property def extents(self): """Return (xmin, xmax, ymin, ymax).""" x0, y0, width, height = self._rect_bbox xmin, xmax = sorted([x0, x0 + width]) ymin, ymax = sorted([y0, y0 + height]) return xmin, xmax, ymin, ymax @extents.setter def extents(self, extents): x1, x2, y1, y2 = extents xmin, xmax = sorted([x1, x2]) ymin, ymax = sorted([y1, y2]) # Update displayed rectangle self._rect.set_x(xmin) self._rect.set_y(ymin) self._rect.set_width(xmax - xmin) self._rect.set_height(ymax - ymin) # Update displayed handles self._corner_handles.set_data(*self.corners) self._edge_handles.set_data(*self.edge_centers) self.set_visible(True) self.redraw() def on_mouse_release(self, event): if event.button != 1: return if not self.ax.in_axes(event): self.eventpress = None return RectangleSelector.release(self, event) self._extents_on_press = None # Undo hiding of rectangle and redraw. self.set_visible(True) self.redraw() self.callback_on_release(self.geometry) def on_mouse_press(self, event): if event.button != 1 or not self.ax.in_axes(event): return self._set_active_handle(event) if self.active_handle is None: # Clear previous rectangle before drawing new rectangle. self.set_visible(False) self.redraw() self.set_visible(True) RectangleSelector.press(self, event) def _set_active_handle(self, event): """Set active handle based on the location of the mouse event""" # Note: event.xdata/ydata in data coordinates, event.x/y in pixels c_idx, c_dist = self._corner_handles.closest(event.x, event.y) e_idx, e_dist = self._edge_handles.closest(event.x, event.y) # Set active handle as closest handle, if mouse click is close enough. if c_dist > self.maxdist and e_dist > self.maxdist: self.active_handle = None return elif c_dist < e_dist: self.active_handle = self._corner_order[c_idx] else: self.active_handle = self._edge_order[e_idx] # Save coordinates of rectangle at the start of handle movement. x1, x2, y1, y2 = self.extents # Switch variables so that only x2 and/or y2 are updated on move. if self.active_handle in ['W', 'SW', 'NW']: x1, x2 = x2, event.xdata if self.active_handle in ['N', 'NW', 'NE']: y1, y2 = y2, event.ydata self._extents_on_press = x1, x2, y1, y2 def on_move(self, event): if self.eventpress is None or not self.ax.in_axes(event): return if self.active_handle is None: # New rectangle x1 = self.eventpress.xdata y1 = self.eventpress.ydata x2, y2 = event.xdata, event.ydata else: x1, x2, y1, y2 = self._extents_on_press if self.active_handle in ['E', 'W'] + self._corner_order: x2 = event.xdata if self.active_handle in ['N', 'S'] + self._corner_order: y2 = event.ydata self.extents = (x1, x2, y1, y2) self.callback_on_move(self.geometry) @property def geometry(self): return self.extents
class LineTool(CanvasToolBase): """Widget for line selection in a plot. Parameters ---------- viewer : :class:`skimage.viewer.Viewer` Skimage viewer object. on_move : function Function called whenever a control handle is moved. This function must accept the end points of line as the only argument. on_release : function Function called whenever the control handle is released. on_enter : function Function called whenever the "enter" key is pressed. maxdist : float Maximum pixel distance allowed when selecting control handle. line_props : dict Properties for :class:`matplotlib.lines.Line2D`. handle_props : dict Marker properties for the handles (also see :class:`matplotlib.lines.Line2D`). Attributes ---------- end_points : 2D array End points of line ((x1, y1), (x2, y2)). """ def __init__(self, viewer, on_move=None, on_release=None, on_enter=None, maxdist=10, line_props=None, handle_props=None, **kwargs): super(LineTool, self).__init__(viewer, on_move=on_move, on_enter=on_enter, on_release=on_release, **kwargs) props = dict(color='r', linewidth=1, alpha=0.4, solid_capstyle='butt') props.update(line_props if line_props is not None else {}) self.linewidth = props['linewidth'] self.maxdist = maxdist self._active_pt = None x = (0, 0) y = (0, 0) self._end_pts = np.transpose([x, y]) self._line = lines.Line2D(x, y, visible=False, animated=True, **props) self.ax.add_line(self._line) self._handles = ToolHandles(self.ax, x, y, marker_props=handle_props) self._handles.set_visible(False) self.artists = [self._line, self._handles.artist] if on_enter is None: def on_enter(pts): x, y = np.transpose(pts) print("length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2)) self.callback_on_enter = on_enter viewer.add_tool(self) @property def end_points(self): return self._end_pts.astype(int) @end_points.setter def end_points(self, pts): self._end_pts = np.asarray(pts) self._line.set_data(np.transpose(pts)) self._handles.set_data(np.transpose(pts)) self._line.set_linewidth(self.linewidth) self.set_visible(True) self.redraw() def hit_test(self, event): if event.button != 1 or not self.ax.in_axes(event): return False idx, px_dist = self._handles.closest(event.x, event.y) if px_dist < self.maxdist: self._active_pt = idx return True else: self._active_pt = None return False def on_mouse_press(self, event): self.set_visible(True) if self._active_pt is None: self._active_pt = 0 x, y = event.xdata, event.ydata self._end_pts = np.array([[x, y], [x, y]]) def on_mouse_release(self, event): if event.button != 1: return self._active_pt = None self.callback_on_release(self.geometry) self.redraw() def on_move(self, event): if event.button != 1 or self._active_pt is None: return if not self.ax.in_axes(event): return self.update(event.xdata, event.ydata) self.callback_on_move(self.geometry) def update(self, x=None, y=None): if x is not None: self._end_pts[self._active_pt, :] = x, y self.end_points = self._end_pts @property def geometry(self): return self.end_points
class LineToolC(CanvasToolBase): """Widget for line selection in a plot. Parameters ---------- ax : :class:`matplotlib.axes.Axes` Matplotlib axes where tool is displayed. on_move : function Function called whenever a control handle is moved. This function must accept the end points of line as the only argument. on_release : function Function called whenever the control handle is released. on_enter : function Function called whenever the "enter" key is pressed. maxdist : float Maximum pixel distance allowed when selecting control handle. line_props : dict Properties for :class:`matplotlib.lines.Line2D`. Attributes ---------- end_points : 2D array End points of line ((x1, y1), (x2, y2)). """ def __init__(self, ax, on_move=None, on_release=None, on_enter=None, maxdist=10, line_props=None): super(LineToolC, self).__init__(ax, on_move=on_move, on_enter=on_enter, on_release=on_release) props = dict(color='r', linewidth=1, alpha=0.25, solid_capstyle='butt') props.update(line_props if line_props is not None else {}) self.linewidth = props['linewidth'] self.maxdist = 10 self.position = 'blah' self._active_pt = None x = (0, 0) y = (0, 0) self._end_pts = np.transpose([x, y]) self._line = lines.Line2D(x, y, visible=False, animated=True, **props) ax.add_line(self._line) self._handles = ToolHandles(ax, x, y) self._handles.set_visible(False) self._artists = [self._line, self._handles.artist] if on_enter is None: def on_enter(pts): x, y = np.transpose(pts) print("length = %0.2f" % np.sqrt(np.diff(x)**2 + np.diff(y)**2)) self.callback_on_enter = on_enter self.connect_event('button_press_event', self.on_mouse_press) self.connect_event('button_release_event', self.on_mouse_release) self.connect_event('motion_notify_event', self.on_move) @property def end_points(self): return self._end_pts @end_points.setter def end_points(self, pts): self._end_pts = np.asarray(pts) self._line.set_data(np.transpose(pts)) self._handles.set_data(np.transpose(pts)) self._line.set_linewidth(self.linewidth) self.set_visible(True) self.redraw() def on_mouse_press(self, event): if event.button != 1 or not self.ax.in_axes(event): return self.set_visible(True) idx, px_dist = self._handles.closest(event.x, event.y) if px_dist < self.maxdist: self._active_pt = idx # else: # self._active_pt = 0 # x, y = event.xdata, event.ydata # self._end_pts = np.array([[x, y], [x, y]]) def distalong(self, anchors, d): x1,y1 = anchors[0].end_points[0] x2,y2 = anchors[1].end_points[1] m = (y2-y1)/(x2-x1) xadj = x1 + math.sqrt((d*d)/(m*m+1)) yadj = m*(xadj - x1)+y1 return [xadj, yadj] def distance(self, xy1, xy2): x1,y1 = xy1 x2,y2 = xy2 return math.sqrt((y1-y2)**2 + (x1-x2)**2) def closest(self, p1, anchors): x3,y3 = p1 x1,y1 = anchors[0].end_points[0] x2,y2 = anchors[1].end_points[1] # print x1,y1,'\n',x2,y2,'\n',x3,y3 m = (y2-y1)/(x2-x1) xadj = (x3+m*(y3-y1)+m*m*x1)/(m*m+1) yadj = m*(xadj - x1)+y1 return [xadj, yadj] def interpolate(self, points): if self.position == 'anchorright': return [self.closest(points[0], self.anchors),points[1]] elif self.position == 'anchorleft': return [points[0],self.closest(points[1], self.anchors)] elif self.position == 'mid': return [self.closest(x, self.anchors) for x in points] elif self.position == 'midspine': return [self.closest(points[0], self.topanchors), self.closest(points[1], self.bottomanchors)] return points def on_mouse_release(self, event): if event.button != 1: return self._active_pt = None self.callback_on_release(self.geometry) def on_move(self, event): # print 'onmove' idx, px_dist = self._handles.closest(event.x, event.y) # if px_dist < self.maxdist: # print px_dist, self.maxdist if event.button != 1 or self._active_pt is None: return if not self.ax.in_axes(event): return self.update(event.xdata, event.ydata) self.callback_on_move(self.geometry) def update(self, x=None, y=None, orig=None): if x is not None: self._end_pts[self._active_pt, :] = x, y if orig is None: px = np.round(self._end_pts) px = self.interpolate(px) self._end_pts = np.asarray(px) self.end_points = self._end_pts if orig == None and self.position == 'anchorleft' or self.position == 'anchorright': fulld = self.distance(self.anchors[0].end_points[0],self.anchors[1].end_points[1]) step = fulld / self.ncells topa = self.midchildren[1].topanchors bota = self.midchildren[1].bottomanchors topd = self.distance(topa[0].end_points[0], topa[1].end_points[1]) botd = self.distance(bota[0].end_points[0], bota[1].end_points[1]) topstep = topd/self.ncells botstep = botd/self.ncells for i,x in enumerate(self.midchildren[1:-1]): x.end_points = [x.distalong(x.topanchors,topstep*(i+1)), x.distalong(x.bottomanchors, botstep*(i+1))] if self.position == 'anchorleft' and self._active_pt == 0: self.end_points = [self.end_points[0], self.distalong(self.anchors,step)] for i,x in enumerate(self.children): if x is not self: if x.position != 'anchorright': x.end_points = [x.distalong(x.anchors,step*i), x.distalong(x.anchors, step*(i+1))] else: x.end_points = [x.distalong(x.anchors,step*i), x.end_points[1]] # x.update(orig = i) if self.position == 'anchorright' and self._active_pt == 1: self.end_points = [self.distalong(self.anchors,step), self.end_points[1]] for i,x in enumerate(self.children): if x is not self: if x.position != 'anchorleft': x.end_points = [x.distalong(x.anchors,step*i), x.distalong(x.anchors, step*(i+1))] else: x.end_points = [x.end_points[0], x.distalong(x.anchors,step)] # x.update(orig = i) @property def geometry(self): return self.end_points
class RectangleTool(CanvasToolBase, RectangleSelector): """Widget for selecting a rectangular region in a plot. After making the desired selection, press "Enter" to accept the selection and call the `on_enter` callback function. Parameters ---------- ax : :class:`matplotlib.axes.Axes` Matplotlib axes where tool is displayed. on_move : function Function called whenever a control handle is moved. This function must accept the rectangle extents as the only argument. on_release : function Function called whenever the control handle is released. on_enter : function Function called whenever the "enter" key is pressed. maxdist : float Maximum pixel distance allowed when selecting control handle. rect_props : dict Properties for :class:`matplotlib.patches.Rectangle`. This class redefines defaults in :class:`matplotlib.widgets.RectangleSelector`. Attributes ---------- extents : tuple Rectangle extents: (xmin, xmax, ymin, ymax). """ def __init__(self, ax, on_move=None, on_release=None, on_enter=None, maxdist=10, rect_props=None): CanvasToolBase.__init__(self, ax, on_move=on_move, on_enter=on_enter, on_release=on_release) props = dict(edgecolor=None, facecolor="r", alpha=0.15) props.update(rect_props if rect_props is not None else {}) if props["edgecolor"] is None: props["edgecolor"] = props["facecolor"] RectangleSelector.__init__(self, ax, lambda *args: None, rectprops=props, useblit=self.useblit) # Alias rectangle attribute, which is initialized in RectangleSelector. self._rect = self.to_draw self._rect.set_animated(True) self.maxdist = maxdist self.active_handle = None self._extents_on_press = None if on_enter is None: def on_enter(extents): print("(xmin=%.3g, xmax=%.3g, ymin=%.3g, ymax=%.3g)" % extents) self.callback_on_enter = on_enter props = dict(mec=props["edgecolor"]) self._corner_order = ["NW", "NE", "SE", "SW"] xc, yc = self.corners self._corner_handles = ToolHandles(ax, xc, yc, marker_props=props) self._edge_order = ["W", "N", "E", "S"] xe, ye = self.edge_centers self._edge_handles = ToolHandles(ax, xe, ye, marker="s", marker_props=props) self._artists = [self._rect, self._corner_handles.artist, self._edge_handles.artist] @property def _rect_bbox(self): x0 = self._rect.get_x() y0 = self._rect.get_y() width = self._rect.get_width() height = self._rect.get_height() return x0, y0, width, height @property def corners(self): """Corners of rectangle from lower left, moving clockwise.""" x0, y0, width, height = self._rect_bbox xc = x0, x0 + width, x0 + width, x0 yc = y0, y0, y0 + height, y0 + height return xc, yc @property def edge_centers(self): """Midpoint of rectangle edges from left, moving clockwise.""" x0, y0, width, height = self._rect_bbox w = width / 2.0 h = height / 2.0 xe = x0, x0 + w, x0 + width, x0 + w ye = y0 + h, y0, y0 + h, y0 + height return xe, ye @property def extents(self): """Return (xmin, xmax, ymin, ymax).""" x0, y0, width, height = self._rect_bbox xmin, xmax = sorted([x0, x0 + width]) ymin, ymax = sorted([y0, y0 + height]) return xmin, xmax, ymin, ymax @extents.setter def extents(self, extents): x1, x2, y1, y2 = extents xmin, xmax = sorted([x1, x2]) ymin, ymax = sorted([y1, y2]) # Update displayed rectangle self._rect.set_x(xmin) self._rect.set_y(ymin) self._rect.set_width(xmax - xmin) self._rect.set_height(ymax - ymin) # Update displayed handles self._corner_handles.set_data(*self.corners) self._edge_handles.set_data(*self.edge_centers) self.set_visible(True) self.redraw() def release(self, event): if event.button != 1: return if not self.ax.in_axes(event): self.eventpress = None return RectangleSelector.release(self, event) self._extents_on_press = None # Undo hiding of rectangle and redraw. self.set_visible(True) self.redraw() self.callback_on_release(self.geometry) def press(self, event): if event.button != 1 or not self.ax.in_axes(event): return self._set_active_handle(event) if self.active_handle is None: # Clear previous rectangle before drawing new rectangle. self.set_visible(False) self.redraw() self.set_visible(True) RectangleSelector.press(self, event) def _set_active_handle(self, event): """Set active handle based on the location of the mouse event""" # Note: event.xdata/ydata in data coordinates, event.x/y in pixels c_idx, c_dist = self._corner_handles.closest(event.x, event.y) e_idx, e_dist = self._edge_handles.closest(event.x, event.y) # Set active handle as closest handle, if mouse click is close enough. if c_dist > self.maxdist and e_dist > self.maxdist: self.active_handle = None return elif c_dist < e_dist: self.active_handle = self._corner_order[c_idx] else: self.active_handle = self._edge_order[e_idx] # Save coordinates of rectangle at the start of handle movement. x1, x2, y1, y2 = self.extents # Switch variables so that only x2 and/or y2 are updated on move. if self.active_handle in ["W", "SW", "NW"]: x1, x2 = x2, event.xdata if self.active_handle in ["N", "NW", "NE"]: y1, y2 = y2, event.ydata self._extents_on_press = x1, x2, y1, y2 def onmove(self, event): if self.eventpress is None or not self.ax.in_axes(event): return if self.active_handle is None: # New rectangle x1 = self.eventpress.xdata y1 = self.eventpress.ydata x2, y2 = event.xdata, event.ydata else: x1, x2, y1, y2 = self._extents_on_press if self.active_handle in ["E", "W"] + self._corner_order: x2 = event.xdata if self.active_handle in ["N", "S"] + self._corner_order: y2 = event.ydata self.extents = (x1, x2, y1, y2) self.callback_on_move(self.geometry) @property def geometry(self): return self.extents