class click_window: '''An interactive window. Given an axis instance and a start point (x0,y0), draw a dynamic rectangle that follows the mouse until the close() function is called (which returns the coordinates of the final rectangle. Useful or selecting out square regions.''' def __init__(self, ax, x0, y0): self.ax = ax self.x0 = x0 self.y0 = y0 self.rect = Rectangle((x0, y0), width=0, height=0, alpha=0.1) ax.add_artist(self.rect) def connect(self): self.cidmotion = self.rect.figure.canvas.mpl_connect( 'motion_notify_event', self.on_motion) def on_motion(self, event): # Have we left the axes? if event.inaxes != self.rect.axes: return self.rect.set_width(event.xdata - self.x0) self.rect.set_height(event.ydata - self.y0) self.ax.figure.canvas.draw() def close(self): self.rect.figure.canvas.mpl_disconnect(self.cidmotion) extent = self.rect.get_bbox().get_points() self.rect.remove() self.ax.figure.canvas.draw() return (list(ravel(extent)))
class click_window: '''An interactive window. Given an axis instance and a start point (x0,y0), draw a dynamic rectangle that follows the mouse until the close() function is called (which returns the coordinates of the final rectangle. Useful or selecting out square regions.''' def __init__(self, ax, x0, y0): self.ax = ax self.x0 = x0 self.y0 = y0 self.rect = Rectangle((x0,y0), width=0, height=0, alpha=0.1) ax.add_artist(self.rect) def connect(self): self.cidmotion = self.rect.figure.canvas.mpl_connect( 'motion_notify_event', self.on_motion) def on_motion(self, event): # Have we left the axes? if event.inaxes != self.rect.axes: return self.rect.set_width(event.xdata - self.x0) self.rect.set_height(event.ydata - self.y0) self.ax.figure.canvas.draw() def close(self): self.rect.figure.canvas.mpl_disconnect(self.cidmotion) extent = self.rect.get_bbox().get_points() self.rect.remove() self.ax.figure.canvas.draw() return(list(ravel(extent)))
class Brusher(): ''' A generic brusher. Expects: fig: a mpl figure with subplots NOTE: each individual point, in each subplot, must be assigned its own color. e.g. use `scatter(x_coords, y_coords, c=array_of_rgba_colors)` data: a tuple of numpy arrays, with one entry for each axis in fig, in order (i.e. left->right then top->bottom) each item in the tuple must by a 2d numpy array with: array[0] --> 1d array of x coordinates array[1] --> 1d array of y coordinates and each list must be in the same order. i.e. the nth pair of values in the first array of the tuple must correspond to the nth pair of values in every other array in the tuple. ''' def __init__(self, fig, data): ''' fig: a pre-rendered figure, with subplot axes data: the data represented in fig, formatted as described above ''' self.fig = fig self.data = data self.axl = fig.get_axes() # internal things to keep track of self.button_down = False self.brush = None self.brush_axis = None self.start_xy = (None, None) # define the colors we want to use (overwrites original figure's colors) self.unbrushed_color = [.5, .5, .5, .5] # this is a transparent gray self.brushed_color = [0., 0., 1., .75] # this is a transparent blue # register events to track self.fig.canvas.mpl_connect('button_press_event', self.button_press) self.fig.canvas.mpl_connect('motion_notify_event', self.mouse_motion) self.fig.canvas.mpl_connect('button_release_event', self.button_release) def button_press(self, event): ''' handles a mouse click ''' # if clicked outside of axes, ignore if not event.inaxes: return # remove old brush object if it's there if self.brush: self.brush.remove() self.unbrush_me() plt.draw() # start a brush here self.button_down = True self.start_xy = (event.xdata,event.ydata) self.brush = Rectangle( self.start_xy, 0,0, facecolor='gray', alpha=.15 ) self.brush_axis = event.inaxes # add brush to axis event.inaxes.add_patch(self.brush) plt.draw() def mouse_motion(self, event): ''' handles the mouse moving around ''' # if we haven't already clicked and started a brush, ignore the motion if not (self.button_down and self.brush): return # if we've moved the mouse outside the starting axis, ignore if self.brush_axis != event.inaxes: return # otherwise, track the motion and update the brush self.brush.set_width( abs(self.start_xy[0] - event.xdata) ) self.brush.set_height( abs(self.start_xy[1] - event.ydata) ) self.brush.set_x( min(self.start_xy[0], event.xdata) ) self.brush.set_y( min(self.start_xy[1], event.ydata) ) plt.draw() def button_release(self, event): ''' handles a mouse button release ''' self.button_down = False # if we didn't start with a brush, ignore if not self.brush: return # if we just clicked without drawing, remove the brush if (event.xdata, event.ydata) == self.start_xy: self.unbrush_me() plt.draw() return # if we stopped inside the same axis, update the brush one last time if event.inaxes == self.brush_axis: self.brush.set_width( abs(self.start_xy[0] - event.xdata) ) self.brush.set_height( abs(self.start_xy[1] - event.ydata) ) self.brush.set_x( min(self.start_xy[0], event.xdata) ) self.brush.set_y( min(self.start_xy[1], event.ydata) ) self.brush_me() plt.draw() def brush_me(self): ''' apply the new brush to data in all axes ''' # figure out the indices of the data inside the brush i_brush_axis = self.axl.index(self.brush_axis) ax_data = self.data[i_brush_axis] bbox = self.brush.get_bbox() boolean_brushed = [] for i in range(len(ax_data[0])): if bbox.contains( ax_data[0,i], ax_data[1,i] ): boolean_brushed.append(True) else: boolean_brushed.append(False) boolean_brushed = np.array(boolean_brushed) # boolean_brushed is an array of T,F values indicating whether the datapoint at # that index should be 'brushed' (i.e. keep its color) or not (made gray) for i, ax in enumerate(self.axl): plotted_things = ax.collections[0] # now set the facecolor of all points not brushed to gray # new_fc is a 2d array, the 0th axis corresponding to individual points, # and the 1st axis corresponding to color values new_fc = plotted_things.get_facecolors() new_fc[~boolean_brushed] = self.unbrushed_color new_fc[boolean_brushed] = self.brushed_color def unbrush_me(self): ''' paint everything the brushed color ''' # build an always-true boolean array and brush to that boolean_brushed = [] for i in range(len(self.data[0][0])): boolean_brushed.append(True) boolean_brushed = np.array(boolean_brushed) for i, ax in enumerate(self.axl): plotted_things = ax.collections[0] new_fc = plotted_things.get_facecolors() new_fc[boolean_brushed] = self.brushed_color
class RectangleCreationOnClick(PanOnClick): """Class providing rectangle creation patch to a matplotlib Figure. Left button to create a rectangle with the desired width. """ def __init__(self, figure=None, scale_factor=1.1): """Initializer :param Figure figure: The matplotlib figure to attach the behavior to. :param float scale_factor: The scale factor to apply on wheel event. """ super(RectangleCreationOnClick, self).__init__(figure, scale_factor=1.1) self._add_connection_rectangle_creation('button_press_event', self._on_mouse_press_creation) self._add_connection_rectangle_creation( 'button_release_event', self._on_mouse_release_creation) self._add_connection_rectangle_creation('motion_notify_event', self._on_mouse_motion_creation) self._pressed_button = None # To store active button self._axes = None # To store x and y axes concerned by interaction self._event = None # To store reference event during interaction def update_current_rectangle(self, ax, curr_event, past_event): """Create the rectangle with the width stablished from initial click and current position of the mouse :param matplotlib.axes._subplots.AxesSubplot ax: axis where the rectangle is gonna be drawed or it's width modified :param matplotlib.backend_bases.MouseEvent curr_event: data from the current position of the mouse of the motion event :param matplotlib.backend_bases.MouseEvent past_event: data from the initial position where a click has been done """ try: #If current click does not goes to the limit (which would cause to return None) if curr_event.xdata != None: yAxis_limits = self._rectangle_yAxis_limits #If rectangle creation goes from left to right if curr_event.xdata < past_event.xdata: #If the rectangle has not been created, create it if not isinstance(self._rectangle_interactions, Rectangle): self._rectangle_interactions = Rectangle( [curr_event.xdata, yAxis_limits[0] * 0.9], abs(past_event.xdata - curr_event.xdata), yAxis_limits[1] * 1.1, color='black', alpha=0.3) self._rectangleStats.direction = -1 ax.add_patch(self._rectangle_interactions) #If it has been created and goes from left to right, update width elif isinstance(self._rectangle_interactions, Rectangle ) and self._rectangleStats.direction == -1: self._rectangle_interactions.set_height( abs(yAxis_limits[0] * 0.9 - yAxis_limits[1] * 1.1)) self._rectangle_interactions.set_y(yAxis_limits[0] * 0.9) self._rectangle_interactions.set_x(curr_event.xdata) self._rectangle_interactions.set_width( abs(past_event.xdata - curr_event.xdata)) #If it has been created but changed direction from right-left to left-right, update width elif self._rectangleStats.direction == 1: self._rectangle_interactions.set_height( abs(yAxis_limits[0] * 0.9 - yAxis_limits[1] * 1.1)) self._rectangle_interactions.set_y(yAxis_limits[0] * 0.9) self._rectangleStats.direction = -1 self._rectangle_interactions.set_x(curr_event.xdata) self._rectangle_interactions.set_width( abs(past_event.xdata - curr_event.xdata)) #If rectangle creation goes from right to left else: #If the rectangle has not been created, create it if not isinstance(self._rectangle_interactions, Rectangle): self._rectangle_interactions = Rectangle( [past_event.xdata, yAxis_limits[0] * 0.9], abs(past_event.xdata - curr_event.xdata), yAxis_limits[1] * 1.1, color='black', alpha=0.3) self._rectangleStats.direction = 1 ax.add_patch(self._rectangle_interactions) #If it has been created and goes from left to right, update width elif isinstance( self._rectangle_interactions, Rectangle) and self._rectangleStats.direction == 1: self._rectangle_interactions.set_height( abs(yAxis_limits[0] * 0.9 - yAxis_limits[1] * 1.1)) self._rectangle_interactions.set_y(yAxis_limits[0] * 0.9) self._rectangle_interactions.set_x(past_event.xdata) self._rectangle_interactions.set_width( abs(past_event.xdata - curr_event.xdata)) #If it has been created but changed direction from right-left to left-right, update width elif self._rectangleStats.direction == -1: self._rectangle_interactions.set_height( abs(yAxis_limits[0] * 0.9 - yAxis_limits[1] * 1.1)) self._rectangle_interactions.set_y(yAxis_limits[0] * 0.9) self._rectangleStats.direction = 1 self._rectangle_interactions.set_x(past_event.xdata) self._rectangle_interactions.set_width( abs(past_event.xdata - curr_event.xdata)) except Exception as e: pass def _rectangle_creation(self, event): """Execute function based on the name of it""" if event.name == 'button_press_event': # begin creation self._original_event = event self._event = event elif event.name == 'button_release_event': # end creation self._event = None pub.sendMessage('rangeData', iw=self._rectangle_interactions.get_x(), ew=self._rectangle_interactions.get_bbox().x1) elif event.name == 'motion_notify_event': # set range for creation if self._event is None: return if event.x != self._event.x: for ax in self._axes[0]: self.update_current_rectangle(ax, event, self._original_event) if event.x != self._event.x: self._draw() self._event = event def _on_mouse_press_creation(self, event): """Set axes values based on point selected""" if self._pressed_button is not None: return # Discard event if a button is already pressed x_axes = set() y_axes = set() for ax in self.figure.axes: #Simmilar to event.inaxis = axis if ax.contains(event)[0]: x_axes.add(ax) y_axes.add(ax) self._axes = x_axes, y_axes self._pressed_button = event.button if self._pressed_button == 1: self._rectangle_creation(event) def _on_mouse_release_creation(self, event): if self._pressed_button == 1: self._rectangle_creation(event) self._pressed_button = None def _on_mouse_motion_creation(self, event): if self._pressed_button == 1: self._rectangle_creation(event)
xyB=(1, 1), coordsA=coords1, coordsB="axes fraction", axesA=gax, axesB=ax2, arrowstyle="-", color="r") ax2.add_artist(con_axes0) ax2.add_artist(con_axes1) ax0 = fig.add_axes( [0.7, 0.12, 0.25, 0.25] ) #inset_axes(fig, width="25%", height="25%", loc=4)##([0.7,0.17,0.25,0.25])#([0.64,0.2,.25,.25]) rectbbox = rect.get_bbox() rect_xy0 = np.array((rectbbox.x0, rectbbox.y1)) #data coords rect_xy1 = np.array((rectbbox.x1, rectbbox.y0)) #data coords ax0_pos = ax0.get_position() ax_xy0 = np.array( (ax0_pos.x0, ax0_pos.y0)) #(0.68,0.11)#ax0_pos.bounds[:2] #axes coords ax_xy1 = np.array( (ax0_pos.x1, ax0_pos.y1)) #(0.68,0.11)#ax0_pos.bounds[:2] #axes coords con_axes2 = ConnectionPatch(xyA=rect_xy0, coordsA="data", xyB=ax_xy0, coordsB=ax2.transAxes, color='w', arrowstyle='-')
class Brusher(): ''' A generic brusher. Expects: fig: a mpl figure with subplots NOTE: each individual point, in each subplot, must be assigned its own color. e.g. use `scatter(x_coords, y_coords, c=array_of_rgba_colors)` data: a tuple of numpy arrays, with one entry for each axis in fig, in order (i.e. left->right then top->bottom) each item in the tuple must by a 2d numpy array with: array[0] --> 1d array of x coordinates array[1] --> 1d array of y coordinates and each list must be in the same order. i.e. the nth pair of values in the first array of the tuple must correspond to the nth pair of values in every other array in the tuple. ''' def __init__(self, fig, data): ''' fig: a pre-rendered figure, with subplot axes data: the data represented in fig, formatted as described above ''' self.fig = fig self.data = data self.axl = fig.get_axes() # internal things to keep track of self.button_down = False self.brush = None self.brush_axis = None self.start_xy = (None, None) # define the colors we want to use (overwrites original figure's colors) self.unbrushed_color = [.5, .5, .5, .5] # this is a transparent gray self.brushed_color = [0., 0., 1., .75] # this is a transparent blue # register events to track self.fig.canvas.mpl_connect('button_press_event', self.button_press) self.fig.canvas.mpl_connect('motion_notify_event', self.mouse_motion) self.fig.canvas.mpl_connect('button_release_event', self.button_release) def button_press(self, event): ''' handles a mouse click ''' # if clicked outside of axes, ignore if not event.inaxes: return # remove old brush object if it's there if self.brush: self.brush.remove() self.unbrush_me() plt.draw() # start a brush here self.button_down = True self.start_xy = (event.xdata, event.ydata) self.brush = Rectangle(self.start_xy, 0, 0, facecolor='gray', alpha=.15) self.brush_axis = event.inaxes # add brush to axis event.inaxes.add_patch(self.brush) plt.draw() def mouse_motion(self, event): ''' handles the mouse moving around ''' # if we haven't already clicked and started a brush, ignore the motion if not (self.button_down and self.brush): return # if we've moved the mouse outside the starting axis, ignore if self.brush_axis != event.inaxes: return # otherwise, track the motion and update the brush self.brush.set_width(abs(self.start_xy[0] - event.xdata)) self.brush.set_height(abs(self.start_xy[1] - event.ydata)) self.brush.set_x(min(self.start_xy[0], event.xdata)) self.brush.set_y(min(self.start_xy[1], event.ydata)) plt.draw() def button_release(self, event): ''' handles a mouse button release ''' self.button_down = False # if we didn't start with a brush, ignore if not self.brush: return # if we just clicked without drawing, remove the brush if (event.xdata, event.ydata) == self.start_xy: self.unbrush_me() plt.draw() return # if we stopped inside the same axis, update the brush one last time if event.inaxes == self.brush_axis: self.brush.set_width(abs(self.start_xy[0] - event.xdata)) self.brush.set_height(abs(self.start_xy[1] - event.ydata)) self.brush.set_x(min(self.start_xy[0], event.xdata)) self.brush.set_y(min(self.start_xy[1], event.ydata)) self.brush_me() plt.draw() def brush_me(self): ''' apply the new brush to data in all axes ''' # figure out the indices of the data inside the brush i_brush_axis = self.axl.index(self.brush_axis) ax_data = self.data[i_brush_axis] bbox = self.brush.get_bbox() boolean_brushed = [] for i in range(len(ax_data[0])): if bbox.contains(ax_data[0, i], ax_data[1, i]): boolean_brushed.append(True) else: boolean_brushed.append(False) boolean_brushed = np.array(boolean_brushed) # boolean_brushed is an array of T,F values indicating whether the datapoint at # that index should be 'brushed' (i.e. keep its color) or not (made gray) for i, ax in enumerate(self.axl): plotted_things = ax.collections[0] # now set the facecolor of all points not brushed to gray # new_fc is a 2d array, the 0th axis corresponding to individual points, # and the 1st axis corresponding to color values new_fc = plotted_things.get_facecolors() new_fc[~boolean_brushed] = self.unbrushed_color new_fc[boolean_brushed] = self.brushed_color def unbrush_me(self): ''' paint everything the brushed color ''' # build an always-true boolean array and brush to that boolean_brushed = [] for i in range(len(self.data[0][0])): boolean_brushed.append(True) boolean_brushed = np.array(boolean_brushed) for i, ax in enumerate(self.axl): plotted_things = ax.collections[0] new_fc = plotted_things.get_facecolors() new_fc[boolean_brushed] = self.brushed_color