class VerticalMarker(QObject): moved = Signal(float) def __init__(self, canvas, x, color): super(VerticalMarker, self).__init__() self.x = x self.ax = canvas.figure.get_axes()[0] y0, y1 = self.ax.get_ylim() path = Path([(x, y0), (x, y1)], [Path.MOVETO, Path.LINETO]) self.patch = PathPatch(path, facecolor='None', edgecolor=color, picker=5, linewidth=2.0, animated=True) self.ax.add_patch(self.patch) self.is_moving = False def remove(self): self.patch.remove() def redraw(self): y0, y1 = self.ax.get_ylim() vertices = self.patch.get_path().vertices vertices[0] = self.x, y0 vertices[1] = self.x, y1 self.ax.draw_artist(self.patch) def get_x_in_pixels(self): x_pixels, _ = self.patch.get_transform().transform((self.x, 0)) return x_pixels def is_above(self, x): return np.abs(self.get_x_in_pixels() - x) < 3 def on_click(self, x): if self.is_above(x): self.is_moving = True def stop(self): self.is_moving = False def mouse_move(self, xd): if self.is_moving and xd is not None: self.x = xd self.moved.emit(xd) def should_override_cursor(self, x): return self.is_moving or self.is_above(x)
class VerticalMarker(QObject): """ An interactive marker displayed as a vertical line. """ x_moved = Signal(float) def __init__(self, canvas, color, x, y0=None, y1=None, line_width=1.0, picker_width=5, line_style='-'): """ Init the marker. :param canvas: A MPL canvas. :param color: An MPL colour value :param x: The x coordinate (data) of the marker. :param y0: The y coordinate (data) of the bottom end of the marker. Default is None which means dynamically set it to the current lowest y value displayed. :param y1: The y coordinate (data) of the top end of the marker. Default is None which means dynamically set it to the current highest y value displayed. :param line_width: The line width (pixels). :param picker_width: The picker sensitivity (pixels). :param line_style: An MPL line style value. """ super(VerticalMarker, self).__init__() self.ax = canvas.figure.get_axes()[0] self.x = x self.y0 = y0 self.y1 = y1 y0, y1 = self._get_y0_y1() path = Path([(x, y0), (x, y1)], [Path.MOVETO, Path.LINETO]) self.patch = PathPatch(path, facecolor='None', edgecolor=color, picker=picker_width, linewidth=line_width, linestyle=line_style, animated=True) self.ax.add_patch(self.patch) self.is_moving = False def _get_y0_y1(self): """ Calculate the current y coordinates of the line ends. :return: Tuple y0, y1. """ if self.y0 is None or self.y1 is None: y0, y1 = self.ax.get_ylim() if self.y0 is not None: y0 = self.y0 if self.y1 is not None: y1 = self.y1 return y0, y1 def remove(self): """ Remove this marker from the canvas. """ self.patch.remove() def redraw(self): """ Redraw this marker. """ y0, y1 = self._get_y0_y1() vertices = self.patch.get_path().vertices vertices[0] = self.x, y0 vertices[1] = self.x, y1 self.ax.draw_artist(self.patch) def get_x_in_pixels(self): """ Get the x coordinate in screen pixels. """ x_pixels, _ = self.patch.get_transform().transform((self.x, 0)) return x_pixels def is_above(self, x, y): """ Check if a mouse positioned at (x, y) is over this marker. :param x: An x mouse coordinate. :param y: An y mouse coordinate. :return: True or False. """ x_pixels, y_pixels = self.patch.get_transform().transform((x, y)) if self.y0 is not None and y < self.y0: return False if self.y1 is not None and y > self.y1: return False return abs(self.get_x_in_pixels() - x_pixels) < 3 def mouse_move_start(self, x, y): """ Start moving this marker if (x, y) is above it. Ignore otherwise. :param x: An x mouse coordinate. :param y: An y mouse coordinate. """ self.is_moving = self.is_above(x, y) def mouse_move_stop(self): """ Stop moving. """ self.is_moving = False def mouse_move(self, x, y=None): """ Move this marker to a new position if movement had been started earlier by a call to mouse_move_start(x, y) :param x: An x mouse coordinate. :param y: An y mouse coordinate. :return: True if moved or False if stayed at the old position. """ if self.is_moving and x is not None: self.x = x self.x_moved.emit(x) return True return False def get_cursor_at_y(self, y): """ Get an override cursor for an y coordinate given that the x == self.x :param y: A y coordinate. :return: QCursor or None. """ return QCursor(Qt.SizeHorCursor) def override_cursor(self, x, y): """ Get the override cursor for mouse position (x, y) :param x: An x mouse coordinate. :param y: An y mouse coordinate. :return: QCursor or None. """ if self.y0 is not None and y < self.y0: return None if self.y1 is not None and y > self.y1: return None if self.is_moving or self.is_above(x, y): return self.get_cursor_at_y(y) return None
class HorizontalMarker(QObject): """ An interactive marker displayed as a horizontal line. """ y_moved = Signal(float) def __init__(self, canvas, color, y, x0=None, x1=None, line_width=1.0, picker_width=5, line_style='-', move_cursor=None, axis=None): """ Init the marker. :param canvas: A MPL canvas. :param color: An MPL colour value :param y: The y coordinate (data) of the marker. :param x0: The x coordinate (data) of the left end of the marker. Default is None which means dynamically set it to the current maximum x value displayed. :param x1: The x coordinate (data) of the right end of the marker. Default is None which means dynamically set it to the current minimum x value displayed. :param line_width: The line width (pixels). :param picker_width: The picker sensitivity (pixels). :param line_style: An MPL line style value. """ super(HorizontalMarker, self).__init__() self.canvas = canvas if axis is None: self.axis = canvas.figure.get_axes()[0] else: self.axis = axis self.y = y self.x0 = x0 self.x1 = x1 x0, x1 = self._get_x0_x1() path = Path([(x0, y), (x1, y)], [Path.MOVETO, Path.LINETO]) self.patch = PathPatch(path, facecolor='None', edgecolor=color, picker=picker_width, linewidth=line_width, linestyle=line_style, animated=True) self.axis.add_patch(self.patch) self.axis.interactive_markers.append(self.patch) self.is_moving = False self.move_cursor = move_cursor def _get_x0_x1(self): """ Calculate the current x coordinates of the line ends. :return: Tuple x0, x1. """ if self.x0 is None or self.x1 is None: x0, x1 = self.axis.get_xlim() if self.x0 is not None: x0 = self.x0 if self.x1 is not None: x1 = self.x1 return x0, x1 def remove(self): """ Remove this marker from the canvas. """ self.patch.remove() def redraw(self): """ Redraw this marker. """ x0, x1 = self._get_x0_x1() vertices = self.patch.get_path().vertices vertices[0] = x0, self.y vertices[1] = x1, self.y self.axis.draw_artist(self.patch) def set_visible(self, visible): self.patch.set_visible(visible) def set_color(self, color): """ Set the colour of the marker :param color: The color to set the marker to. """ self.patch.set_edgecolor(color) def get_position(self): """ Get the y coordinate in axes coords. :return: y in axes coords """ return self.y def set_position(self, y): """ Set the y position of the marker. :param y: An y axis coordinate. """ self.y = y self.y_moved.emit(y) def get_y_in_pixels(self): """ Returns the y coordinate in screen pixels. :return: y in pixels """ _, y_pixels = self.patch.get_transform().transform((0, self.y)) return y_pixels def is_above(self, x, y): """ Check if a mouse positioned at (x, y) is over this marker. :param x: An x mouse coordinate. :param y: An y mouse coordinate. :return: True or False. """ _, y_pixels = self.patch.get_transform().transform((x, y)) if self.x0 is not None and x < self.x0: return False if self.x1 is not None and x > self.x1: return False return abs(self.get_y_in_pixels() - y_pixels) < MARKER_SENSITIVITY def mouse_move_start(self, x, y): """ Start moving this marker if (x, y) is above it. Ignore otherwise. :param x: An x mouse coordinate. :param y: An y mouse coordinate. :param pixels: True if the coordinates are already in pixels. """ self.is_moving = self.is_above(x, y) def mouse_move_stop(self): """ Stop moving. """ self.is_moving = False def mouse_move(self, x, y): """ Move this marker to a new position if movement had been started earlier by a call to mouse_move_start(x, y) :param x: An x mouse coordinate. :param y: An y mouse coordinate. :return: True if moved or False if stayed at the old position. """ if self.is_moving and y is not None and x is not None: self.set_position(y) return True return False def is_marker_moving(self): """ Returns true if the marker is being moved :return: True if the marker is being moved. """ return self.is_moving def override_cursor(self, x, y): """ Get the override cursor for mouse position (x, y) :param x: An x mouse coordinate. :param y: An y mouse coordinate. :return: QCursor or None. """ if self.x0 is not None and x < self.x0: return None if self.x1 is not None and x > self.x1: return None if self.is_moving or self.is_above(x, y): return self.move_cursor if self.move_cursor is not None else QCursor( Qt.SizeVerCursor) return None def set_move_cursor(self, cursor, x_pos, y_pos): """Set the style of the cursor to use when the marker is moving""" if cursor is not None: cursor = QCursor(cursor) self.move_cursor = cursor self.override_cursor(x_pos, y_pos)