def make_path(d, style): items = [] for c in d.split(): if c.upper() in codemap: items.append(c) else: x, y = (float(v) for v in c.split(",")) items.append((x, y)) codes = [] vertices = [] i = 0 lx, ly = 0, 0 last_code = "M" while i < len(items): code = items[i] if not isinstance(code, str): code = last_code else: i += 1 ucode = code.upper() if code.isupper(): relative = False else: relative = True if ucode in ("M", "L"): x, y = items[i] i += 1 if relative: x += lx y += ly codes.append(codemap[ucode]) vertices.append((x, y)) lx, ly = x, y if ucode == "C": if not relative: points = items[i:i + 3] else: points = [(_x + lx, _y + ly) for _x, _y in items[i:i + 3]] codes.extend([codemap[ucode]] * 3) vertices.extend(points) lx, ly = points[-1] i += 3 if ucode == "Z": break last_code = code codes[0] = Path.MOVETO patch = PathPatch(Path(vertices, codes)) patch.set_linewidth(get_number(style.get("stroke-width", "1px"))) fill = style.get("fill", "none") if fill == "none": patch.set_fill(None) else: patch.set_facecolor(fill) edge = style.get("stroke", "none") patch.set_edgecolor(edge) return patch
def make_path(d, style): items = [] for c in d.split(): if c.upper() in codemap: items.append(c) else: x, y = (float(v) for v in c.split(",")) items.append((x, y)) codes = [] vertices = [] i = 0 lx, ly = 0, 0 last_code = "M" while i < len(items): code = items[i] if not isinstance(code, str): code = last_code else: i += 1 ucode = code.upper() if code.isupper(): relative = False else: relative = True if ucode in ("M", "L"): x, y = items[i] i += 1 if relative: x += lx y += ly codes.append(codemap[ucode]) vertices.append((x, y)) lx, ly = x, y if ucode == "C": if not relative: points = items[i:i+3] else: points = [(_x + lx, _y + ly) for _x, _y in items[i:i+3]] codes.extend([codemap[ucode]]*3) vertices.extend(points) lx, ly = points[-1] i += 3 if ucode == "Z": break last_code = code codes[0] = Path.MOVETO patch = PathPatch( Path(vertices, codes) ) patch.set_linewidth( get_number(style.get("stroke-width", "1px") ) ) fill = style.get("fill", "none") if fill == "none": patch.set_fill( None ) else: patch.set_facecolor( fill ) edge = style.get("stroke", "none") patch.set_edgecolor(edge) return patch
def _render_on_subplot(self, subplot): """ Render this Bezier path in a subplot. This is the key function that defines how this Bezier path graphics primitive is rendered in matplotlib's library. TESTS:: sage: bezier_path([[(0,1),(.5,0),(1,1)]]) Graphics object consisting of 1 graphics primitive :: sage: bezier_path([[(0,1),(.5,0),(1,1),(-3,5)]]) Graphics object consisting of 1 graphics primitive """ from matplotlib.patches import PathPatch from matplotlib.path import Path from sage.plot.misc import get_matplotlib_linestyle options = dict(self.options()) del options['alpha'] del options['thickness'] del options['rgbcolor'] del options['zorder'] del options['fill'] del options['linestyle'] bpath = Path(self.vertices, self.codes) bpatch = PathPatch(bpath, **options) options = self.options() bpatch.set_linewidth(float(options['thickness'])) bpatch.set_fill(options['fill']) bpatch.set_zorder(options['zorder']) a = float(options['alpha']) bpatch.set_alpha(a) c = to_mpl_color(options['rgbcolor']) bpatch.set_edgecolor(c) bpatch.set_facecolor(c) bpatch.set_linestyle( get_matplotlib_linestyle(options['linestyle'], return_type='long')) subplot.add_patch(bpatch)
def _render_on_subplot(self, subplot): """ Render this Bezier path in a subplot. This is the key function that defines how this Bezier path graphics primitive is rendered in matplotlib's library. TESTS:: sage: bezier_path([[(0,1),(.5,0),(1,1)]]) Graphics object consisting of 1 graphics primitive :: sage: bezier_path([[(0,1),(.5,0),(1,1),(-3,5)]]) Graphics object consisting of 1 graphics primitive """ from matplotlib.patches import PathPatch from matplotlib.path import Path from sage.plot.misc import get_matplotlib_linestyle options = dict(self.options()) del options['alpha'] del options['thickness'] del options['rgbcolor'] del options['zorder'] del options['fill'] del options['linestyle'] bpath = Path(self.vertices, self.codes) bpatch = PathPatch(bpath, **options) options = self.options() bpatch.set_linewidth(float(options['thickness'])) bpatch.set_fill(options['fill']) bpatch.set_zorder(options['zorder']) a = float(options['alpha']) bpatch.set_alpha(a) c = to_mpl_color(options['rgbcolor']) bpatch.set_edgecolor(c) bpatch.set_facecolor(c) bpatch.set_linestyle(get_matplotlib_linestyle(options['linestyle'], return_type='long')) subplot.add_patch(bpatch)
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='-', move_cursor=None, axis=None): """ 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.canvas = canvas if axis is None: self.axis = canvas.figure.get_axes()[0] else: self.axis = axis 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.axis.add_patch(self.patch) self.axis.interactive_markers.append(self.patch) self.is_moving = False self.move_cursor = move_cursor 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.axis.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.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 x coordinate in axes coords. :return: x in axes coords """ return self.x def set_position(self, x): """ Set the x position of the marker. :param x: An x axis coordinate. """ self.x = x self.x_moved.emit(x) def get_x_in_pixels(self): """ Get the x coordinate in screen pixels. :return: x in 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, _ = 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) < 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. """ 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.set_position(x) 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 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 self.move_cursor if self.move_cursor is not None else 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 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)
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)