def plot_bboxes(self, ax: Axes, boxes: torch.Tensor, class_idxs: torch.Tensor, color: str): """Add bboxes to the plot tied to `ax`""" for box, class_idx in zip(boxes, class_idxs): xmin, ymin, xmax, ymax = box width = xmax - xmin height = ymax - ymin rect = patches.Rectangle((xmin, ymin), width, height, fill=False, color=color, lw=1.5) ax.add_patch(rect) if isinstance(class_idx, torch.Tensor): class_idx = class_idx.item() class_name = self.categories[class_idx] if class_idx else None ax.text(xmin, ymin, class_name, bbox={ 'facecolor': 'white', 'alpha': 0.5, 'pad': 2 })
def draw_demand(ax: axes.Axes, coord: types.Coord2D, color: str, active: Optional[bool] = None) -> List[mpatches.Patch]: '''Draw a demand/arrivals source :param ax: matplotlib axes on which to draw :param coord: coordinate at which to place demand source (bottom left of it) :param color: color of the demand source :param active: whether demand is active (are items arriving) ''' d_width = 1 d_height = 1 alpha = 0.3 if not active else .8 centre = [coord[0] + d_width / 2 + 0.02, coord[1] + d_height / 2] p1 = mpatches.RegularPolygon(centre, 7, radius=0.5, color=color, alpha=alpha) ax.add_patch(p1) p2 = mpatches.RegularPolygon( centre, 7, radius=0.52, fill=False, color='k', ) ax.add_patch(p2) return [p1, p2]
def plot_rect(axes: Axes, rect, highlight_corner=False, **kwargs): from matplotlib.patches import Rectangle, Polygon defaults = dict(fill=True, edgecolor='#929591', facecolor='#d8dcd6', highlight_fill=True, highlight_facecolor='#d8dcd6') for k in defaults: if k not in kwargs: kwargs[k] = defaults[k] if 'alpha' in kwargs: from matplotlib.colors import to_rgba kwargs['edgecolor'] = to_rgba(kwargs['edgecolor'], kwargs['alpha']) kwargs['facecolor'] = to_rgba(kwargs['facecolor'], kwargs['alpha']) kwargs['highlight_facecolor'] = to_rgba(kwargs['highlight_facecolor'], kwargs['alpha']) highlight_facecolor = kwargs.pop('highlight_facecolor') highlight_fill = kwargs.pop('highlight_fill') x, y, theta = rect.get_pose() corner = rect.get_world_point((-rect.width / 2, -rect.height / 2)) p1 = axes.add_patch(Rectangle(xy=corner, angle=math.degrees(theta), width=rect.width, height=rect.height, **kwargs)) if highlight_corner: return p1, axes.add_patch(Polygon(xy=np.array(rect.vertices[0:3]), fill=highlight_fill, facecolor=highlight_facecolor)) else: return p1
def draw_rectangle_around_text(scene: Axes, origin: tuple, width: int, height: int, fill_color: tuple, line_color: tuple, line_width: int, rounded: int, text: str, font_size: int, font_family: str): if rounded > 0: box_style = 'round' else: box_style = 'square' scene.add_patch( FancyBboxPatch( origin, width, height, facecolor='#{0:02x}{1:02x}{2:02x}'.format(*fill_color), edgecolor='#{0:02x}{1:02x}{2:02x}'.format(*line_color), linewidth=line_width, boxstyle=box_style)) # write label in the middle under labelx = origin[0] + width // 2 labely = origin[ 1] + height // 2 + 4 # TODO: Should be drawn in the vertical center, so + 4 not needed! scene.text(labelx, labely, s=text, fontsize=font_size, color='#{0:02x}{1:02x}{2:02x}'.format(*line_color), fontname=font_family) return origin[0], origin[1], width, height
def render(self, axis: Axes, view: np.ndarray = np.eye(3), normalize: bool = False, colors: Tuple = ('b', 'r', 'k'), linewidth: float = 2) -> None: """ Renders the box in the provided Matplotlib axis. :param axis: Axis onto which the box should be drawn. :param view: <np.array: 3, 3>. Define a projection in needed (e.g. for drawing projection in an image). :param normalize: Whether to normalize the remaining coordinate. :param colors: (<Matplotlib.colors>: 3). Valid Matplotlib colors (<str> or normalized RGB tuple) for front, back and sides. :param linewidth: Width in pixel of the box sides. """ # corners = view_points(self.corners(), view, normalize=normalize)[:2, :] # def draw_rect(selected_corners, color): # prev = selected_corners[-1] # for corner in selected_corners: # axis.plot([prev[0], corner[0]], [prev[1], corner[1]], color=color, linewidth=linewidth) # prev = corner # # x = [corners.T[0][0], corners.T[4][0], corners.T[5][0], corners.T[1][0],corners.T[0][0]] # y = [corners.T[0][1], corners.T[4][1], corners.T[5][1], corners.T[1][1],corners.T[0][1]] # axis.plot(x,y,color=colors[2], linewidth=linewidth) # axis.add_patch(rect) corners = view_points(self.corners(), view, normalize=normalize)[:2, :] corners = corners.T corners = corners[(0, 4, 5, 1), :] axis.add_patch(Polygon(corners, closed=True, facecolor=colors[0]))
def _draw_buildings(buildings: List[bl.Building], ax: maxs.Axes) -> None: for building in buildings: if isinstance(building.geometry, Polygon): patch = PolygonPatch(building.geometry, facecolor="black", edgecolor="black") ax.add_patch(patch)
def _draw_nodes_to_change(nodes_to_change: List[bl.NodeInRectifyBuilding], ax: maxs.Axes) -> None: for node in nodes_to_change: ax.add_patch( pat.Circle((node.my_node.original_x, node.my_node.original_y), radius=0.4, linewidth=2, color='green', fill=False))
def add_list_of_polygons(ax: maxs.Axes, polygons: List[shg.Polygon], face_color: str, edge_color: str) -> None: for poly in polygons: if poly is None or not poly.is_valid or not poly.is_valid: continue if isinstance(poly, shg.Polygon) or isinstance(poly, shg.MultiPolygon): patch = PolygonPatch(poly, facecolor=face_color, edgecolor=edge_color) ax.add_patch(patch)
def candlestick_chart( ax: Axes, df: DataFrame, *, heikin_ashi: bool = False, colorup: str = 'forestgreen', colordown: str = 'orangered', width: float = .6, alpha: float = 1., ) -> None: # The area between the open and the close prices is called the body, # price excursions above and below the body are shadows (also called # wicks). Wicks illustrate the highest and lowest traded prices of an # asset during the time interval represented. The body illustrates the # opening and closing trades. This entire structure is called a candle. offset = width / 2. prev = None for t, row in df.iterrows(): t = date2num(t) o0, h0, l0, c0 = row['Open'], row['High'], row['Low'], row['Close'] if heikin_ashi: # pylint: disable=W8201 if prev is None: oP, hP, lP, cP = o0, h0, l0, c0 else: oP, hP, lP, cP = prev c = (o0 + h0 + l0 + c0) / 4. o = (oP + cP) / 2. h = max(h0, o, c) l = min(l0, o, c) prev = o, h, l, c else: o, h, l, c = o0, h0, l0, c0 # pylint: disable=W8201 if c >= o: color = colorup lower = o upper = c else: color = colordown lower = c upper = o height = upper - lower ax.add_line( Line2D(xdata=(t, t), ydata=(lower, l), color=color, alpha=alpha)) ax.add_line( Line2D(xdata=(t, t), ydata=(upper, h), color=color, alpha=alpha)) ax.add_patch( Rectangle(xy=(t - offset, lower), width=width, height=height, facecolor=color, edgecolor=color, alpha=alpha))
def _draw_city_blocks(building_zones: List[m.BuildingZone], ax: maxs.Axes) -> None: for zone in building_zones: city_blocks = zone.linked_city_blocks for item in city_blocks: red = random.random() green = random.random() blue = random.random() patch = PolygonPatch(item.geometry, facecolor=(red, green, blue), edgecolor=(red, green, blue)) ax.add_patch(patch)
def _add_patch_for_building_zone(building_zone: m.BuildingZone, face_color: str, edge_color: str, ax: maxs.Axes) \ -> None: if isinstance(building_zone.geometry, MultiPolygon): for polygon in building_zone.geometry.geoms: patch = PolygonPatch(polygon, facecolor=face_color, edgecolor=edge_color) ax.add_patch(patch) else: patch = PolygonPatch(building_zone.geometry, facecolor=face_color, edgecolor=edge_color) ax.add_patch(patch)
def axis_rectangle_labeling(axes: Axes, coordinates, colors, lw=None, fill=False): """ Labeling axes with given bounding boxes. """ for (x, y, w, h), c in zip(coordinates, colors): rect = plt.Rectangle((x - w / 2, y - h / 2), w, h, color=c, fill=fill, linewidth=lw) axes.add_patch(rect) return axes
def _draw_background_zones(building_zones, ax: maxs.Axes) -> None: for building_zone in building_zones: my_color = "lightgray" if isinstance(building_zone, m.GeneratedBuildingZone): my_color = "darkgray" if isinstance(building_zone.geometry, MultiPolygon): for polygon in building_zone.geometry.geoms: patch = PolygonPatch(polygon, facecolor=my_color, edgecolor="white") ax.add_patch(patch) else: patch = PolygonPatch(building_zone.geometry, facecolor=my_color, edgecolor="white") ax.add_patch(patch)
def draw_line(scene: Axes, start: tuple, ctrl1: tuple, ctrl2: tuple, end: tuple, is_curved: bool, edge_color: tuple): if is_curved: # cubic Bezier curve scene.add_patch( PathPatch( Path([start, ctrl1, ctrl2, end], [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4]), edgecolor='#{0:02x}{1:02x}{2:02x}'.format(*edge_color), facecolor=(1, 1, 1, 0), # Transparent... linewidth=1)) # TODO: WIDTH! else: scene.add_patch( PathPatch( Path([start, end], [Path.MOVETO, Path.LINETO]), edgecolor='#{0:02x}{1:02x}{2:02x}'.format(*edge_color), facecolor=(1, 1, 1, 0), # Transparent... linewidth=1)) # TODO: WIDTH!
def plot_spheres(ax: Axes, points: array_like, r: float): """ Plot two-dimensional view of spheres centered on points. Parameters ---------- ax: Axes Matplotlib Axes object. points : array_like Points in space. r : float Radius of spheres. """ for point in points: circle = plt.Circle((point[0], point[1]), radius=r, color="black", fill=False) ax.add_patch(circle)
def plot_circle(axes: Axes, circle, **kwargs): from matplotlib.patches import Circle defaults = dict(fill=True, edgecolor='#929591', facecolor='#d8dcd6') for k in defaults: if k not in kwargs: kwargs[k] = defaults[k] return axes.add_patch(Circle(xy=circle.get_position(), radius=circle.get_radius(), **kwargs))
def draw_reset(ax: axes.Axes, width: float, height: float) -> None: """ Fully reset a plot by deleting all annotations and patches, and drawing a blank background :param ax: matplotlib axes on which to draw :param width: the width of the plot :param height: the height of the plot """ for child in ax.get_children(): if isinstance(child, mtext.Annotation): child.remove() for p in reversed(ax.patches): p.remove() p1 = mpatches.Rectangle((0, 0), width, height, fill=True, color='w') ax.add_patch(p1)
def _draw_btg_building_zones(btg_building_zones, ax: maxs.Axes) -> None: for item in btg_building_zones: if item.type_ in [ enu.BuildingZoneType.btg_builtupcover, enu.BuildingZoneType.btg_urban ]: my_color = "magenta" elif item.type_ in [ enu.BuildingZoneType.btg_town, enu.BuildingZoneType.btg_suburban ]: my_color = "gold" else: my_color = "yellow" patch = PolygonPatch(item.geometry, facecolor=my_color, edgecolor=my_color) ax.add_patch(patch)
def draw_straight_arrows(ax: axes.Axes, from_coord: types.Coord2D, to_coord: types.Coord2D, color: str, active: bool, effect: int, do_annotations: bool, no_head: bool = False) -> List[mpatches.Patch]: """Draw a straight arrow between two points, indicating an activity. :param ax: matplotlib axes on which to draw :param from_coord: point at the tail of the arrow (bottom left of it) :param to_coord: point at the head of the arrow (bottom left of it) :param color: color of the arrow when active :param active: whether arrow is active (are items arriving) :param effect: what value/no of items are being processed along the arrow :param do_annotations: whether to annotate the arrow with the no of items being processd :param no_head: whether do suppress a head to arrow """ alpha = 0.3 if not active else 1 color = 'k' if not active else color if no_head: a_style = '-' do_annotations = False else: a_style = mpatches.ArrowStyle("-|>", head_length=7, head_width=4) mid = 0.5 mid_from_coord = [from_coord[0], from_coord[1] + mid] mid_to_coord = [to_coord[0], to_coord[1] + mid] p1 = mpatches.FancyArrowPatch(mid_from_coord, mid_to_coord, arrowstyle=a_style, alpha=alpha, color=color) ax.add_patch(p1) if do_annotations and active: msg = '{:.0f}'.format(abs(effect)) text_coord = [(from_coord[0] + to_coord[0]) * .5 - 0.6, to_coord[1] + 1.] ax.annotate(msg, from_coord, text_coord, size=14, color=color) return [p1]
def _draw_blocked_areas(building_zones, ax: maxs.Axes) -> None: for my_building_zone in building_zones: for blocked in my_building_zone.linked_blocked_areas: if m.BlockedAreaType.open_space == blocked.type_: my_facecolor = 'darkgreen' my_edgecolor = 'darkgreen' elif m.BlockedAreaType.gen_building == blocked.type_: my_facecolor = 'yellow' my_edgecolor = 'yellow' elif m.BlockedAreaType.osm_building == blocked.type_: my_facecolor = 'blue' my_edgecolor = 'blue' else: my_facecolor = 'orange' my_edgecolor = 'orange' patch = PolygonPatch(blocked.polygon, facecolor=my_facecolor, edgecolor=my_edgecolor) ax.add_patch(patch)
def draw_polygon(ax: Axes, polygon: Polygon, size: int = size, ticks: int = ticks, font_size: int = font_size) -> None: """Plot a polygon to the current matplotlib figure.""" max_x, min_x = ceil(max(p.x for p in polygon.points)), floor(min(p.x for p in polygon.points)) max_y, min_y = ceil(max(p.y for p in polygon.points)), floor(min(p.y for p in polygon.points)) size_x = max_x - min_x size_y = max_y - min_y ax.set_aspect('equal') ax.set_xlim(min_x - size_x * 0.05, max_x + size_x * 0.05) ax.set_ylim(min_y - size_y * 0.05, max_y + size_y * 0.05) ticks -= 1 ax.xaxis.set_major_locator(ticker.MultipleLocator(base=round(size_x / ticks))) ax.yaxis.set_major_locator(ticker.MultipleLocator(base=round(size_y / ticks))) ax.grid(color='grey') polygon_points = list(polygon.points_as_tuples()) plt_polygon = pyplot.Polygon(polygon_points, fill=None, color='0.25', linewidth=.5) ax.add_patch(plt_polygon) pyplot.plot(list([x[0] for x in polygon_points]), list([x[1] for x in polygon_points]), color='black', linestyle='None', marker='.', markersize=3) if isinstance(polygon, TriangulatedPolygon): edges = set() for triangle in polygon.triangles: for edge in triangle.edges: if (edge.a.index, edge.b.index) in edges: # The edge was already plotted continue if edge.a.index in ((edge.b.index - 1) % len(polygon), (edge.b.index + 1) % len(polygon)): # The edge is a polygon edge, thus we ignore it continue pyplot.plot([edge.a.x, edge.b.x], [edge.a.y, edge.b.y], color='0.5', linestyle='--', alpha=0.5) edges.add((edge.a.index, edge.b.index)) edges.add((edge.b.index, edge.a.index)) for i in range(0, len(polygon), 2): ax.annotate(str(i), xy=polygon_points[i], xytext=( polygon_points[i][0] + round(size_x / ticks) / 20, polygon_points[i][1] + round(size_y / ticks) / 20), fontsize=font_size, color='0.3', alpha=.8)
def plot(self, ax: Axes) -> Axes: """Plot the curve.""" xmin, xmax = ax.get_xlim() ymin, ymax = ax.get_ylim() if not self.x_data or not self.y_data or not _between_limits( self.x_data, self.y_data, xmin, xmax, ymin, ymax): self._print_err( '{} (label:{}) Not between limits ([{}, {}, {}, {}]) ' '-> x:{}, y:{}'.format(self._type_curve, self._label, xmin, xmax, ymin, ymax, self.x_data, self.y_data)) return ax if self._is_patch and self.y_data is not None: assert len(self.y_data) > 2 verts = list(zip(self.x_data, self.y_data)) codes = ([Path.MOVETO] + [Path.LINETO] * (len(self.y_data) - 2) + [Path.CLOSEPOLY]) path = Path(verts, codes) patch = patches.PathPatch(path, **self.style) ax.add_patch(patch) if self._label is not None: bbox_p = path.get_extents() text_x = .5 * (bbox_p.x0 + bbox_p.x1) text_y = .5 * (bbox_p.y0 + bbox_p.y1) style = { 'ha': 'center', 'va': 'center', "backgroundcolor": [1, 1, 1, .4] } if 'edgecolor' in self.style: style['color'] = mod_color(self.style['edgecolor'], -25) self._annotate_label(ax, self._label, text_x, text_y, 0, style) else: ax.plot(self.x_data, self.y_data, **self.style) if self._label is not None: self.add_label(ax) return ax
def _draw_settlement_zones(building_zones: List[m.BuildingZone], ax: maxs.Axes) -> None: for building_zone in building_zones: colour = 'grey' if building_zone.type_ is enu.BuildingZoneType.farmyard: colour = 'brown' _add_patch_for_building_zone(building_zone, colour, colour, ax) for block in building_zone.linked_city_blocks: if block.settlement_type is enu.SettlementType.centre: colour = 'blue' elif block.settlement_type is enu.SettlementType.block: colour = 'green' elif block.settlement_type is enu.SettlementType.dense: colour = 'magenta' elif block.settlement_type is enu.SettlementType.periphery: colour = 'yellow' patch = PolygonPatch(block.geometry, facecolor=colour, edgecolor=colour) if block.settlement_type_changed: patch.set_hatch('/') ax.add_patch(patch)
def plot(self, ax: Axes) -> Axes: """Plot the curve.""" xmin, xmax = ax.get_xlim() ymin, ymax = ax.get_ylim() if (self.x_data is None or self.y_data is None or not _between_limits( self.x_data, self.y_data, xmin, xmax, ymin, ymax)): logging.info( f"{self._type_curve} (label:{self._label}) Not between limits " f"([{xmin}, {xmax}, {ymin}, {ymax}]) " f"-> x:{self.x_data}, y:{self.y_data}") return ax if self._is_patch and self.y_data is not None: assert len(self.y_data) > 2 verts = list(zip(self.x_data, self.y_data)) codes = ([Path.MOVETO] + [Path.LINETO] * (len(self.y_data) - 2) + [Path.CLOSEPOLY]) path = Path(verts, codes) patch = patches.PathPatch(path, **self.style) ax.add_patch(patch) if self._label is not None: bbox_p = path.get_extents() text_x = 0.5 * (bbox_p.x0 + bbox_p.x1) text_y = 0.5 * (bbox_p.y0 + bbox_p.y1) style = { "ha": "center", "va": "center", "backgroundcolor": [1, 1, 1, 0.4], } if "edgecolor" in self.style: style["color"] = mod_color(self.style["edgecolor"], -25) self._annotate_label(ax, self._label, text_x, text_y, 0, style) else: ax.plot(self.x_data, self.y_data, **self.style) if self._label is not None: self.add_label(ax) return ax
def plot(self, ax: Axes): """ Plots the lens into the passed matplotlib axes Args: ax: (Axes) the axes to plot the lens into Returns: (tuple) plotted objects """ plotted_objects = Element.plot(self, ax) plotted_objects += plotting.plot_aperture(ax, self) if plot_blockers: plotted_objects += plotting.plot_blocker(ax, self, self.blocker_diameter) if self.draw_arcs: arc_ratio = 0.02 arc_radius_factor = (0.5 * arc_ratio + 0.125 * 1. / arc_ratio) m = np.array( [[self.diameter * (arc_radius_factor - arc_ratio), 0], [-self.diameter * (arc_radius_factor - arc_ratio), 0]]) m = self.points_to_global_frame_of_reference(m) r = arc_radius_factor * self.diameter a = 2 * np.arctan(2. * arc_ratio) * 180. / np.pi arc = Arc(m[0, :], 2 * r, 2 * r, self.theta, 180. - a, 180. + a, **plotting.outline_properties.copy()) ax.add_patch(arc) plotted_objects += (arc, ) arc = Arc(m[1, :], 2 * r, 2 * r, self.theta, -a, +a, **plotting.outline_properties.copy()) ax.add_patch(arc) plotted_objects += (arc, ) return plotted_objects
def _draw_blocked_nodes(blocked_nodes: List[bl.NodeInRectifyBuilding], ax: maxs.Axes) -> None: for node in blocked_nodes: if bl.RectifyBlockedType.ninety_degrees in node.blocked_types: my_color = 'blue' else: my_color = 'red' if bl.RectifyBlockedType.corner_to_bow in node.blocked_types: my_fill = True else: my_fill = False if bl.RectifyBlockedType.multiple_buildings in node.blocked_types: my_alpha = 0.3 else: my_alpha = 1.0 my_circle = pat.Circle( (node.my_node.original_x, node.my_node.original_y), radius=0.4, linewidth=2, color=my_color, fill=my_fill, alpha=my_alpha) ax.add_patch(my_circle)
def plot_polygon(axes: Axes, polygon, **kwargs): from matplotlib.patches import Polygon defaults = dict(fill=True, # edgecolor='#929591', facecolor='#d8dcd6') for k in defaults: if k not in kwargs: kwargs[k] = defaults[k] if 'alpha' in kwargs: from matplotlib.colors import to_rgba # kwargs['edgecolor'] = to_rgba(kwargs['edgecolor'], kwargs['alpha']) kwargs['facecolor'] = to_rgba(kwargs['facecolor'], kwargs['alpha']) artist = axes.add_patch(Polygon(xy=np.array(polygon.plot_vertices), **kwargs)) return artist
def draw_buffer(ax: axes.Axes, coord: types.Coord2D, num_items: int, max_capacity: int, color: str, do_annotations: bool, do_reverse: bool = False, is_extra_high: Optional[bool] = False) -> List[mpatches.Patch]: """Draw a horizontal buffer diagram on axes, at a certain coordinate, with items added from the left ('top' is on the left.) :param ax: matplotlib axes on which to draw :param coord: coordinate at which to place buffer (bottom left of it) :param num_items: number of items in the buffer (state) :param max_capacity: max capacity of the buffer :param color: color of the buffer :param do_annotations: whether to annotate the buffer with the current state :param do_reverse: if true, draw the buffer in reverse (items added from right) :param is_extra_high: if true, draw a buffer that is twice the height of the default option """ b_width = 3 if is_extra_high: b_height = 2 else: b_height = 1 p1 = mpatches.Rectangle(coord, b_width, b_height, color=color) fill_fraction = num_items / max_capacity f_width = (1 - fill_fraction) * b_width ax.add_patch(p1) if do_reverse: white_coord = (coord[0] + b_width - f_width, coord[1]) p2 = mpatches.Rectangle(white_coord, f_width, b_height, color='w') else: p2 = mpatches.Rectangle(coord, f_width, b_height, color='w') ax.add_patch(p2) p3 = mpatches.Rectangle(coord, b_width, b_height, fill=False, color='k') ax.add_patch(p3) if do_annotations: msg = '{:.0f}'.format(num_items) text_coord = [coord[0] + .2, coord[1] + b_height + 0.2] ax.annotate(msg, coord, text_coord, size=14) return [p1, p2, p3]
def draw_station(ax: axes.Axes, coord: types.Coord2D, buffers: int, color: str, active_b: Optional[int] = None) -> List[mpatches.Patch]: """Draw a vertical station which holds a resource. :param ax: matplotlib axes on which to draw :param coord: coordinate at which to place station (bottom left of it) :param buffers: number of buffers attached to the station :param color: color of the station :param active_b: which buffer/action is active (where the resource should be) """ s_width = 1 s_height = buffers + (buffers - 1) * 2 margin_height = 0.5 rec_coord = [coord[0], coord[1] - margin_height] p1 = mpatches.Rectangle(rec_coord, s_width, s_height + 2 * margin_height, fill=False, color='k') ax.add_patch(p1) if active_b is None: centre = [coord[0] + s_width / 2 + 0.02, coord[1] + s_height / 2] else: b = active_b - 1 centre = [coord[0] + s_width / 2 + 0.02, coord[1] + 0.5 + 3 * b] p2 = mpatches.Circle(centre, s_width * .42, fill=False, color='k') ax.add_patch(p2) p3 = mpatches.Circle(centre, s_width * .4, color=color, alpha=0.4) ax.add_patch(p3) return [p1, p2, p3]
class InteractiveGraph(object): def __init__(self, ax): self.ax = Axes(ax.get_figure(), ax.get_position(original=True)) self.ax.set_aspect("equal") self.ax.set_anchor("NE") self._visible_vertices, self._visible_edges = {}, {} self._hidden_vertices, self._hidden_edges = {}, {} self._press_action = "move" self._press_actions = { "move": None, } @property def press_actions(self): return self._press_actions.keys() def add_press_action(self, name, handler): self._press_actions[name] = handler def remove_press_action(self, name): del self._press_actions[name] def set_press_action(self, name): if name not in self._press_actions: raise Exception("Invalid action") self._press_action = name def do_press_action(self, vxid): self._press_actions[self._press_action](vxid) def add_vertex(self, vxid, xy, label, redraw=True, **props): if self.vertex_exists(vxid): raise DuplicateVertexError(vxid) circle = plt.Circle(xy, **props) self.ax.add_patch(circle) vx = Vertex(vxid, self, circle, label, props) vx._connect() self._visible_vertices[vxid] = vx if redraw: self.ax.figure.canvas.draw() def update_vertex_props(self, vxid, redraw=True, **props): vx = self.get_vertex(vxid) vx.update_circle_props(**props) if redraw: self.ax.figure.canvas.draw() def restore_vertex_props(self, vxid, redraw=True): vx = self.get_vertex(vxid) vx.restore_circle_props() if redraw: self.ax.figure.canvas.draw() def add_edge(self, edge_id, src_id, tgt_id, redraw=True, **props): if not self.vertex_exists(src_id): raise NonexistentVertexError(src_id, "add edge") elif not self.vertex_exists(tgt_id): raise NonexistentVertexError(tgt_id, "add edge") if self.edge_exists(edge_id): edge = self.get_edge(edge_id) src, tgt = edge.source, edge.target raise DuplicateEdgeError(edge_id, src, tgt, "add edge", "edge id already exists") if src_id == tgt_id: src = self.get_vertex(src_id) src.add_loop(edge_id) else: src, tgt = self.get_vertex(src_id), self.get_vertex(tgt_id) src.add_out_edge(edge_id) tgt.add_in_edge(edge_id) edge = Edge(edge_id, self, src_id, tgt_id, **props) if self.vertex_visible(src_id) and self.vertex_visible(tgt_id): self._visible_edges[edge_id] = edge else: self._hidden_edges[edge_id] = edge def hide_vertex(self, vxid, redraw=True): if not self.vertex_exists(vxid): raise NonexistentVertexError(vxid, "hide") elif not self.vertex_visible(vxid): raise VertexActionError(vxid, "hide", "vertex already hidden") vertex = self.get_vertex(vxid) self._hidden_vertices[vxid] = self._visible_vertices.pop(vxid) for edge_id in vertex.loops & self.visible_edges: self._hidden_edges[edge_id] = self._visible_edges.pop(edge_id) for edge_id in vertex.in_edges & self.visible_edges: self._hidden_edges[edge_id] = self._visible_edges.pop(edge_id) self.get_edge(edge_id).hide() for edge_id in vertex.out_edges & self.visible_edges: self._hidden_edges[edge_id] = self._visible_edges.pop(edge_id) self.get_edge(edge_id).hide() vertex.hide() if redraw: self.ax.figure.canvas.draw() def hide_edge(self, edge_id, redraw=True): if not self.edge_exists(edge_id): raise NonexistentEdgeError(edge_id, None, None, "hide") elif not self.edge_visible(edge_id): raise EdgeActionError(edge_id, "hide", "edge already hidden") self._hidden_edges[edge_id] = self._visible_edges.pop(edge_id) edge = self.get_edge(edge_id) if edge.source != edge.target: edge.hide() if redraw: self.ax.figure.canvas.draw() def restore_vertex(self, vxid, redraw=True): if not self.vertex_exists(vxid): raise NonexistentVertexError(vxid, "restore") if self.vertex_visible(vxid): raise VertexActionError(vxid, "restore", "vertex already visible") vertex = self.get_vertex(vxid) for edge_id in vertex.in_edges: edge = self.get_edge(edge_id) if self.vertex_visible(edge.source): self._visible_edges[edge_id] = self._hidden_edges.pop(edge_id) edge.restore(self.ax) for edge_id in vertex.out_edges: edge = self.get_edge(edge_id) if self.vertex_visible(edge.target): self._visible_edges[edge_id] = self._hidden_edges.pop(edge_id) edge.restore(self.ax) for edge_id in vertex.loops: self._visible_edges[edge_id] = self._hidden_edges.pop(edge_id) self._visible_vertices[vxid] = self._hidden_vertices.pop(vxid) vertex.restore(self.ax) if redraw: self.ax.figure.canvas.draw() def restore_edge(self, edge_id, redraw=True): if not self.edge_exists(edge_id): raise NonexistentEdgeError(edge_id, None, None, "restore") elif self.edge_visible(edge_id): edge = self.get_edge(edge_id) src_id, tgt_id = edge.source, edge.target raise EdgeActionError(edge_id, src_id, tgt_id, "restore", "edge already visible") edge = self.get_edge(edge_id) src_id, tgt_id = edge.source, edge.target if not self.vertex_visible(src_id): raise EdgeActionError(edge_id, src_id, tgt_id, "restore", "source vertex is hidden") if not self.vertex_visible(tgt_id): raise EdgeActionError(edge_id, src_id, tgt_id, "restore", "target vertex is hidden") self._visible_edges[edge_id] = self._hidden_edges.pop(edge_id) if src_id != tgt_id: edge.restore(self.ax) if redraw: self.ax.figure.canvas.draw() def remove_vertex(self, vxid, redraw=True): if not self.vertex_exists(vxid): raise NonexistentVertexError(vxid, "remove") vertex = self._visible_vertices[vxid] for edge_id in vertex.loops & self.visible_edges: edge = self._visible_edges.pop(edge_id) for edge_id in vertex.loops & self.hidden_edges: edge = self._hidden_edges.pop(edge_id) for edge_id in (vertex.in_edges | vertex.out_edges) & self.visible_edges: edge = self._visible_edges.pop(edge_id) edge.hide() for edge_id in (vertex.in_edges | vertex.out_edges) & self.hidden_edges: edge = self._hidden_edges.pop(edge_id) edge.hide() if self.vertex_visible: self._visible_vertices.pop(vxid) else: self._hidden_vertices.pop(vxid) vertex.remove() if redraw: self.ax.figure.canvas.draw() def remove_edge(self, edge_id, redraw=True): if not self.edge_exists(edge_id): raise NonexistentEdgeError(edge_id, "remove") if self.edge_visible(edge_id): edge = self._visible_edges.pop(edge_id) else: edge = self._hidden_edges.pop(edge_id) src_id, tgt_id = edge.source, edge.target src, tgt = self.get_vertex(src_id), self.get_vertex(tgt_id) if src_id == tgt_id: src.remove_loop(edge_id) else: src.remove_out_edge(edge_id) tgt.remove_in_edge(edge_id) edge.hide() if redraw: self.ax.figure.canvas.draw() def redraw(action): def f(self, *args, **kwargs): result = action(self, *args, **kwargs) self.ax.figure.canvas.draw() return result return f @redraw def add_vertices(self, vertices, **props): return filter( lambda v: v is not None, [self.add_vertex(*vx, redraw=False, **props) for vx in vertices]) @redraw def update_vertices_props(self, vertices, **props): return filter(lambda v: v is not None, [ self.update_vertex_props(vx, redraw=False, **props) for vx in vertices ]) @redraw def restore_vertices_props(self, vertices): return filter( lambda v: v is not None, [self.restore_vertex_props(vx, redraw=False) for vx in vertices]) @redraw def add_edges(self, edges, **props): return filter( lambda v: v is not None, [self.add_edge(*e, redraw=False, **props) for e in edges]) @redraw def hide_vertices(self, vertices): return filter(lambda v: v is not None, [self.hide_vertex(vx, False) for vx in vertices]) @redraw def hide_edges(self, edge_ids): return filter(lambda v: v is not None, [self.hide_edge(e, False) for e in edge_ids]) @redraw def restore_vertices(self, vertices): return filter(lambda v: v is not None, [self.restore_vertex(vx, False) for vx in vertices]) @redraw def restore_edges(self, edge_ids): return filter(lambda v: v is not None, [self.restore_edge(e, False) for e in edge_ids]) @redraw def remove_vertices(self, vertices): return filter(lambda v: v is not None, [self.remove_vertex(vx, False) for vx in vertices]) @redraw def remove_edges(self, edge_ids): return filter(lambda v: v is not None, [self.remove_edge(e, False) for e in edge_ids]) @redraw def restore_all(self): return filter( lambda v: v is not None, [self.restore_vertex(vx, False) for vx in self.hidden_vertices] + [self.restore_edge(e, False) for e in self.hidden_edges]) @redraw def clear(self): return filter(lambda v: v is not None, [ self.remove_vertex(vx, False) for vx in self._hidden_vertices.keys() + self._visible_vertices.keys() ] + [ self.remove_edge(e, False) for e in self._hidden_edges.keys() + self._visible_edges.keys() ]) @property def vertices(self): return set(self._visible_vertices.keys()) | set( self._hidden_vertices.keys()) @property def edges(self): return set(self._visible_edges.keys()) | set(self._hidden_edges.keys()) @property def visible_vertices(self): return set(self._visible_vertices.keys()) @property def visible_edges(self): return set(self._visible_edges.keys()) @property def hidden_vertices(self): return set(self._hidden_vertices.keys()) @property def hidden_edges(self): return set(self._hidden_edges.keys()) def vertex_exists(self, vxid): return vxid in self.vertices def edge_exists(self, edge_id): return edge_id in self.edges def vertex_visible(self, vxid): return vxid in self.visible_vertices def edge_visible(self, edge_id): return edge_id in self.visible_edges def get_vertex(self, vxid): if vxid in self._visible_vertices: return self._visible_vertices[vxid] elif vxid in self._hidden_vertices: return self._hidden_vertices[vxid] else: raise NonexistentVertexError(vxid, "get") def get_edge(self, edge_id): if edge_id in self._visible_edges: return self._visible_edges[edge_id] elif edge_id in self._hidden_edges: return self._hidden_edges[edge_id] else: raise NonexistentVertexError(vxid, "get") def get_edges(self, vertices): edges = set() for vxid in vertices: vertex = self.get_vertex(vxid) for edge_id in vertex.out_edges: edge = self.get_edge(edge_id) if edge.target in vertices: edges.add(edge_id) return edges def filter_edges(self, vxid, vertices): edges = set() vertex = self.get_vertex(vxid) for edge_id in vertex.edges: edge = self.get_edges(edge_id) if edge.source in vertices or edge.target in vertices: edges.add(edge_id) return edges def reset_view(self): self.ax.set_autoscale_on(True) self.ax.relim() self.ax.autoscale_view() self.ax.figure.canvas.toolbar.update()