def make_artists(self, axes, show_intruder): 'make self.artists_dict' assert self.vec_list is not None posa_list = [(v[0], v[1], v[2]) for v in self.vec_list] posb_list = [(v[3], v[4], v[5]) for v in self.vec_list] pos_lists = [posa_list, posb_list] if show_intruder: pos_lists.append(posb_list) for i, pos_list in enumerate(pos_lists): x, y, theta = pos_list[0] l = axes.plot(*zip(*pos_list), f'c-', lw=0, zorder=1)[0] l.set_visible(False) self.artists_dict[f'line{i}'] = l if i == 0: lc = LineCollection([], lw=2, animated=True, color='k', zorder=1) axes.add_collection(lc) self.artists_dict[f'lc{i}'] = lc # only sim_index = 0 gets intruder aircraft if i == 0 or (i == 1 and show_intruder): size = State.plane_size box = Bbox.from_bounds(x - size/2, y - size/2, size, size) tbox = TransformedBbox(box, axes.transData) box_image = BboxImage(tbox, zorder=2) theta_deg = (theta - math.pi / 2) / math.pi * 180 # original image is facing up, not right img_rotated = ndimage.rotate(State.img, theta_deg, order=1) box_image.set_data(img_rotated) axes.add_artist(box_image) self.artists_dict[f'plane{i}'] = box_image if i == 0: dot = axes.plot([x], [y], f'k.', markersize=6.0, zorder=2)[0] self.artists_dict[f'dot{i}'] = dot rad = 1500 c = patches.Ellipse((x, y), rad, rad, color='k', lw=3.0, fill=False) axes.add_patch(c) self.artists_dict[f'circle{i}'] = c
def _compute_bbox(self, fig, kw): """ Compute the tight bounding box for each figure once, reducing number of required canvas draw calls from N*2 to N+1 as a function of the number of frames. Tight bounding box computing code here mirrors: matplotlib.backend_bases.FigureCanvasBase.print_figure as it hasn't been factored out as a function. """ fig_id = id(fig) if kw['bbox_inches'] == 'tight': if not fig_id in MPLRenderer.drawn: fig.set_dpi(self.dpi) renderer = fig._cachedRenderer bbox_inches = fig.get_tightbbox(renderer) bbox_artists = kw.pop("bbox_extra_artists", []) bbox_artists += fig.get_default_bbox_extra_artists() bbox_filtered = [] for a in bbox_artists: bbox = a.get_window_extent(renderer) if isinstance(bbox, tuple): continue if a.get_clip_on(): clip_box = a.get_clip_box() if clip_box is not None: bbox = Bbox.intersection(bbox, clip_box) clip_path = a.get_clip_path() if clip_path is not None and bbox is not None: clip_path = clip_path.get_fully_transformed_path() bbox = Bbox.intersection(bbox, clip_path.get_extents()) if bbox is not None and (bbox.width != 0 or bbox.height != 0): bbox_filtered.append(bbox) if bbox_filtered: _bbox = Bbox.union(bbox_filtered) trans = Affine2D().scale(1.0 / self.dpi) bbox_extra = TransformedBbox(_bbox, trans) bbox_inches = Bbox.union([bbox_inches, bbox_extra]) pad = plt.rcParams['savefig.pad_inches'] bbox_inches = bbox_inches.padded(pad) MPLRenderer.drawn[fig_id] = bbox_inches kw['bbox_inches'] = bbox_inches else: kw['bbox_inches'] = MPLRenderer.drawn[fig_id] return kw
def mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs): """ Draw a box to mark the location of an area represented by an inset axes. This function draws a box in *parent_axes* at the bounding box of *inset_axes*, and shows a connection with the inset axes by drawing lines at the corners, giving a "zoomed in" effect. Parameters ---------- parent_axes : `matplotlib.axes.Axes` Axes which contains the area of the inset axes. inset_axes : `matplotlib.axes.Axes` The inset axes. loc1, loc2 : {1, 2, 3, 4} Corners to use for connecting the inset axes and the area in the parent axes. **kwargs Patch properties for the lines and box drawn: %(Patch)s Returns ------- pp : `matplotlib.patches.Patch` The patch drawn to represent the area of the inset axes. p1, p2 : `matplotlib.patches.Patch` The patches connecting two corners of the inset axes and its area. """ rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData) fill = kwargs.pop("fill", False) pp = BboxPatch(rect, fill=fill, **kwargs) parent_axes.add_patch(pp) p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1, **kwargs) inset_axes.add_patch(p1) p1.set_clip_on(False) p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2, **kwargs) inset_axes.add_patch(p2) p2.set_clip_on(False) return pp, p1, p2
def update_artists(self, axes): '''update artists in self.artists_dict to be consistant with self.vec, returns a list of artists''' assert self.artists_dict rv = [] x1, y1, theta1, x2, y2, theta2, _ = self.vec for i, x, y, theta in zip([0, 1], [x1, x2], [y1, y2], [theta1, theta2]): key = f'plane{i}' if key in self.artists_dict: plane = self.artists_dict[key] rv.append(plane) if plane.get_visible(): theta_deg = (theta - math.pi / 2) / math.pi * 180 # original image is facing up, not right original_size = list(State.img.shape) img_rotated = ndimage.rotate(State.img, theta_deg, order=1) rotated_size = list(img_rotated.shape) ratios = [r / o for r, o in zip(rotated_size, original_size)] plane.set_data(img_rotated) size = State.plane_size width = size * ratios[0] height = size * ratios[1] box = Bbox.from_bounds(x - width/2, y - height/2, width, height) tbox = TransformedBbox(box, axes.transData) plane.bbox = tbox key = f'dot{i}' if key in self.artists_dict: dot = self.artists_dict[f'dot{i}'] cir = self.artists_dict[f'circle{i}'] rv += [dot, cir] dot.set_data([x], [y]) cir.set_center((x, y)) # line collection lc = self.artists_dict['lc0'] rv.append(lc) self.update_lc_artist(lc) return rv
def adjust_tight_bbox(self, pad=0.1, extra_artists=None): bbox_inches = self.figure.get_tightbbox(self.renderer) bbox_artists = self.figure.get_default_bbox_extra_artists() if extra_artists is None: extra_artists = [] extra_artists.extend([ax.get_legend() for ax in self.figure.axes if ax.get_legend()]) bbox_artists.extend(extra_artists) bbox_filtered = [] for a in bbox_artists: bbox = a.get_window_extent(self.renderer) if a.get_clip_on(): clip_box = a.get_clip_box() if clip_box is not None: bbox = Bbox.intersection(bbox, clip_box) clip_path = a.get_clip_path() if clip_path is not None and bbox is not None: clip_path = clip_path.get_fully_transformed_path() bbox = Bbox.intersection(bbox, clip_path.get_extents()) if bbox is not None and (bbox.width != 0 or bbox.height != 0): bbox_filtered.append(bbox) if bbox_filtered: _bbox = Bbox.union(bbox_filtered) trans = Affine2D().scale(1.0 / self.figure.dpi) bbox_extra = TransformedBbox(_bbox, trans) bbox_inches = Bbox.union([bbox_inches, bbox_extra]) if pad: bbox_inches = bbox_inches.padded(pad) rect = (np.array(bbox_inches.bounds).reshape(-1,2) / self.figure.get_size_inches()).flatten() # Adjust the rect; values <0 to +; + to zero xpad = -np.min((rect[0], (1-rect[2]))) xpad = 0 if xpad < 0 else xpad ypad = -np.min((rect[1], (1-rect[3]))) ypad = 0 if ypad < 0 else ypad rect = np.array([ xpad, ypad, 1-xpad, 1-ypad ]) self.figure.tight_layout(rect=np.abs(rect))
def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): # enlarge the image by these margins sx, sy = self.image_stretch # create a bounding box to house the image bb = Bbox.from_bounds(xdescent - sx, ydescent - sy, width + sx, height + sy) tbb = TransformedBbox(bb, trans) image = BboxImage(tbb) image.set_data(self.image_data) self.update_prop(image, orig_handle, legend) return [image]
def get_transform(self): # If we don't override this, the transform includes LogTransforms # and the final image gets warped to be 'correct' in data space # since Matplotlib 2.x: # # https://matplotlib.org/users/prev_whats_new/whats_new_2.0.0.html#non-linear-scales-on-image-plots # # However, we want pixels to always visually be the same size, so we # override the transform to not include the LogTransform components. xmin, xmax = self._ax.get_xlim() ymin, ymax = self._ax.get_ylim() bbox = BboxTransformFrom(TransformedBbox(Bbox([[xmin, ymin], [xmax, ymax]]), IDENTITY)) return bbox + self._ax.transAxes
def set_bbox_to_anchor(self, bbox, transform=None): """ Set the bbox that the legend will be anchored to. Parameters ---------- bbox : `~matplotlib.transforms.BboxBase` or tuple The bounding box can be specified in the following ways: - A `.BboxBase` instance - A tuple of ``(left, bottom, width, height)`` in the given transform (normalized axes coordinate if None) - A tuple of ``(left, bottom)`` where the width and height will be assumed to be zero. - *None*, to remove the bbox anchoring, and use the parent bbox. transform : `~matplotlib.transforms.Transform`, optional A transform to apply to the bounding box. If not specified, this will use a transform to the bounding box of the parent. """ if bbox is None: self._bbox_to_anchor = None return elif isinstance(bbox, BboxBase): self._bbox_to_anchor = bbox else: try: l = len(bbox) except TypeError as err: raise ValueError("Invalid argument for bbox : %s" % str(bbox)) from err if l == 2: bbox = [bbox[0], bbox[1], 0, 0] self._bbox_to_anchor = Bbox.from_bounds(*bbox) if transform is None: transform = BboxTransformTo(self.parent.bbox) self._bbox_to_anchor = TransformedBbox(self._bbox_to_anchor, transform) self.stale = True
def _draw_text_with_rotation(self, x0, y0, w, h, txt, offset_x=0, offset_y=0, fg="black", rotation=0, debug=False): ''' draw text txt at coordinates (x0, y0) and width w, height h (additional x offset offset_x and y offset offset_y if desired) with rotation and color fg ''' if debug: self.draw_rectangle(x0 - w / 2.0, y0 - h / 2, x0 + w / 2.0, y0 + h / 2.0, fill='none', outline='red') clip_rect = mpatches.Rectangle(xy=[x0 - w / 2.0, y0 - h / 2.0], width=w, height=0.1, transform=self._ax.transData) textobj = plt.text(x0 + offset_x, y0 + offset_y, txt, color=fg, ha='left', va='center', clip_path=clip_rect, clip_on=True, rotation=rotation) # Let's store the clipping box in the object, so that we can use it # when clipping text textobj._clip = TransformedBbox(bbox=Bbox( ((x0 - w / 2.0, y0 - h / 2.0), (x0 + w / 2, y0 + h / 2.0))), transform=self._ax.transData) textobj.set_rotation_mode('anchor')
def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): l = matplotlib.lines.Line2D([xdescent+self.offset,xdescent+(width-self.space)/3.+self.offset], [ydescent+height/2., ydescent+height/2.]) l.update_from(orig_handle) l.set_clip_on(False) l.set_transform(trans) bb = Bbox.from_bounds(xdescent +(width+self.space)/3.+self.offset, ydescent, height*self.image_data.shape[1]/self.image_data.shape[0], height) tbb = TransformedBbox(bb, trans) image = BboxImage(tbb) image.set_data(self.image_data) self.update_prop(image, orig_handle, legend) return [l,image]
def connect_bbox(bbox1, bbox2, loc1, loc2=None): """ Construct a `.Path` connecting corner *loc1* of *bbox1* to corner *loc2* of *bbox2*, where parameters behave as documented as for the `.BboxConnector` constructor. """ if isinstance(bbox1, Rectangle): bbox1 = TransformedBbox(Bbox.unit(), bbox1.get_transform()) if isinstance(bbox2, Rectangle): bbox2 = TransformedBbox(Bbox.unit(), bbox2.get_transform()) if loc2 is None: loc2 = loc1 x1, y1 = BboxConnector.get_bbox_edge_pos(bbox1, loc1) x2, y2 = BboxConnector.get_bbox_edge_pos(bbox2, loc2) return Path([[x1, y1], [x2, y2]])
def initalize(): mpl.rcParams['lines.linewidth'] = 1 plt.rcParams.update({'font.size': 6}) theta = np.linspace(0, 2 * np.pi, 150) x1 = r1 * np.cos(theta) x2 = r1 * np.sin(theta) ax1.plot(x1, x2) x1 = r2 * np.cos(theta) x2 = r2 * np.sin(theta) ax1.plot(x1, x2) x = np.linspace(0, 3, 100) for i in range(51): y = np.repeat(i, 100) ax2.plot(x, y, color='black') y = np.linspace(0, 50, 100) for i in range(4): x = np.repeat(i, 100) ax2.plot(x, y, color='black') x = np.linspace(3, 4, 10) y = np.repeat(49, 10) ax2.plot(x, y, color='black') y = np.repeat(50, 10) ax2.plot(x, y, color='black') x = np.repeat(4, 10) y = np.linspace(49, 50, 10) ax2.plot(x, y, color='black') mpl.rcParams['lines.linewidth'] = 0.5 for i, m in enumerate(sorted_meals): x = i // 50 + 0.01 y = 49 - i % 50 + 0.1 x2 = x + 1 y2 = y + 1 box = TransformedBbox(Bbox([[x, y], [x2, y2]]), ax2.transData) t = ax2.annotate(m, xy=(x, y), clip_box=box) bb = t.get_window_extent(renderer=rend) if bb.width > max_width: overs.append(i) clipped.append(t)
def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): # save original visibility and then make it visible orig_vis = orig_handle.get_visible() orig_handle.set_visible(1) # set correct state and image data if not orig_vis: image_data = VisibilityHandler._unchecked self.state = False else: image_data = VisibilityHandler._checked self.state = True # ratio for square checkbox image_ratio = image_data.shape[1] / image_data.shape[0] # create a checkbox artist bb = Bbox.from_bounds(xdescent, ydescent, height * image_ratio, height) tbb = TransformedBbox(bb, trans) image = BboxImage(tbb) image.set_data(image_data) # update self self.update_prop(image, orig_handle, legend) self.set_events(image, orig_handle) # artists to be returned artists = [image] # if a handler is given, create artists to be return if self.handler is not None: artists += self.handler.create_artists(legend, orig_handle, xdescent - (height * 2.), ydescent, width - (height * 2.), height, fontsize, trans) # revert visibility orig_handle.set_visible(orig_vis) return artists
def plotBWImage(x, y, im, ax, lims=(3, 3)): """ Plots image im on a x,y scatter plot :param x: x plot location :param y: y plot location :param im: bw image to be plotted, should be a 2D numpy matrix :param ax: axis to plot the image on :param lims: (xlim,ylim) tuple where |x| > xlim and |y| > ylim are not plotted :returns: matplotlib.image.BboxImage handle to the plotted image or None if x,y outside of lims """ if np.abs(x) > lims[0] or np.abs(y) > lims[1]: return bb = Bbox.from_bounds(x, y, 0.1, 0.1) bb2 = TransformedBbox(bb, ax.transData) bbox_image = BboxImage(bb2, norm=None, origin=None, clip_on=False) bbox_image.set_data(im) ax.add_artist(bbox_image)
def scatter_image(feature_x, feature_y, image_paths, title, save=None, code_list=None): """ Args: feature_x: x座標 feature_y: y座標 image_paths: """ global Scale fig = plt.figure() ax = fig.add_subplot(111) xlim = [np.min(feature_x)-5, np.max(feature_x)+5] ylim = [feature_y.min()-5, feature_y.max()+5] for (x, y, path) in zip(feature_x, feature_y, image_paths): img = plt.imread(path) if EmpCode != "" and get_class ( path ) == EmpCode : img = frame_image ( img, 30, 0 ) elif code_list != None : idx = code_list.index ( get_class (path) ) img = frame_image ( img, 30, float(idx) / len(code_list), cmap=cm ) disp_size = max ( xlim[1]-xlim[0], ylim[1]-ylim[0] ) / Num bb = Bbox.from_bounds(x, y, disp_size*Scale, disp_size * Scale) bb2 = TransformedBbox(bb, ax.transData) bbox_image = BboxImage(bb2, cmap=None, norm=None, origin=None, clip_on=False) bbox_image.set_data(img) ax.add_artist(bbox_image) ax.set_ylim(*ylim) ax.set_xlim(*xlim) plt.title(title) if save is not None: plt.savefig(save, dpi=600) plt.show()
def zoom_effect(ax1, ax2, **kwargs): """ ax1 : the main axes ax1 : the zoomed axes Connect ax1 and ax2. The xmin & xmax will be taken from the ax1.viewLim. """ tt = ax1.transScale + (ax1.transLimits + ax2.transAxes) trans = blended_transform_factory(ax2.transData, tt) mybbox1 = ax1.bbox mybbox2 = TransformedBbox(ax1.viewLim, trans) prop_patches = kwargs.copy() prop_patches["ec"] = "none" prop_patches["alpha"] = 0.2 c1, c2, bbox_patch1, bbox_patch2, patch = connect_bbox( mybbox1, mybbox2, loc1a=3, loc2a=2, loc1b=4, loc2b=1, prop_lines=kwargs, prop_patches=prop_patches, ) ax1.add_patch(bbox_patch1) ax2.add_patch(bbox_patch2) ax2.add_patch(c1) ax2.add_patch(c2) ax2.add_patch(patch) return c1, c2, bbox_patch1, bbox_patch2, patch
def create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans): l = Line2D([xdescent + self.offset], [ydescent + height / 2.], c=self.color, ls="", marker="o", mfc=self.color, mec=self.color) l.set_clip_on(False) bb = Bbox.from_bounds( xdescent + (width + self.space) / 3. + self.offset, ydescent, height * self.image_data.shape[1] / self.image_data.shape[0], height) tbb = TransformedBbox(bb, trans) image = BboxImage(tbb) image.set_data(self.image_data) image.set_alpha(1.0) legend.set_alpha(1.0) self.update_prop(image, orig_handle, legend) return [l, image]
def main(argv): parser = argparse.ArgumentParser(prog='VIZ') parser.add_argument('source', help='path to the source metadata file') args = parser.parse_args(argv[1:]) # read in the data file data = pandas.read_csv(args.source, sep='\t') # load up data vis_x = data['x'].tolist() vis_y = data['y'].tolist() img_data = data['filename'].tolist() # Create figure fig = plt.figure() ax = fig.add_subplot(111) for x, y, filepath in zip(vis_x, vis_y, img_data): im = plt.imread(filepath) bb = Bbox.from_bounds(x, y, 1, 1) bb2 = TransformedBbox(bb, ax.transData) bbox_image = BboxImage(bb2, norm=None, origin=None, clip_on=False) bbox_image.set_data(im) ax.add_artist(bbox_image) #plt.scatter(vis_x, vis_y, marker='s', c=vis_y) #plt.colorbar(ticks=range(10)) #plt.clim(-0.5, 9.5) # Set the x and y limits ax.set_ylim(-50, 50) ax.set_xlim(-50, 50) plt.show()
def connect_bbox(bbox1, bbox2, loc1, loc2=None): """ Helper function to obtain a Path from one bbox to another. Parameters ---------- bbox1, bbox2 : `matplotlib.transforms.Bbox` Bounding boxes to connect. loc1 : {1, 2, 3, 4} Corner of *bbox1* to use. Valid values are:: 'upper right' : 1, 'upper left' : 2, 'lower left' : 3, 'lower right' : 4 loc2 : {1, 2, 3, 4}, optional Corner of *bbox2* to use. If None, defaults to *loc1*. Valid values are:: 'upper right' : 1, 'upper left' : 2, 'lower left' : 3, 'lower right' : 4 Returns ------- path : `matplotlib.path.Path` A line segment from the *loc1* corner of *bbox1* to the *loc2* corner of *bbox2*. """ if isinstance(bbox1, Rectangle): bbox1 = TransformedBbox(Bbox.unit(), bbox1.get_transform()) if isinstance(bbox2, Rectangle): bbox2 = TransformedBbox(Bbox.unit(), bbox2.get_transform()) if loc2 is None: loc2 = loc1 x1, y1 = BboxConnector.get_bbox_edge_pos(bbox1, loc1) x2, y2 = BboxConnector.get_bbox_edge_pos(bbox2, loc2) return Path([[x1, y1], [x2, y2]])
ncol = 2 nrow = len(maps) // ncol + 1 xpad_fraction = 0.3 dx = 1. / (ncol + xpad_fraction * (ncol - 1)) ypad_fraction = 0.3 dy = 1. / (nrow + ypad_fraction * (nrow - 1)) for i, m in enumerate(maps): ix, iy = divmod(i, nrow) bbox0 = Bbox.from_bounds(ix * dx * (1 + xpad_fraction), 1. - iy * dy * (1 + ypad_fraction) - dy, dx, dy) bbox = TransformedBbox(bbox0, ax2.transAxes) bbox_image = BboxImage(bbox, cmap=plt.get_cmap(m), norm=None, origin=None, **kwargs) bbox_image.set_data(a) ax2.add_artist(bbox_image) plt.show() ############################################################################# # # .. admonition:: References
def plot_uncertainty(y_true, y_pred, background_data): """ Plots the samples over background data :param y_true: ground truth :param y_pred: model samples :param background_data: numpy array containing the positions with data """ _, ax = plt.subplots() # Flips y (imshow assumes 0 on top, growing down) y_true[:, 1] = -y_true[:, 1] + GRID_SIZE y_pred[:, 1] = -y_pred[:, 1] + GRID_SIZE # Plots the background in black and black (white = positions with data) ax.imshow(-background_data, cmap='Greys', vmin=-1.0, vmax=0.0, alpha=MAP_ALPHA) # Plots the MC Dropout samples in blue, with some transparency ax.scatter(x=y_pred[:, 0, :], y=y_pred[:, 1, :], c='b', s=3, alpha=0.15, edgecolors='none', label="MC Dropout samples") # Plots the true position in solid red ax.scatter(x=y_true[:, 0], y=y_true[:, 1], c='r', s=5, edgecolors='none', label="True positions") ax.legend(loc="upper right", labelcolor=['b', 'r'], fontsize="small") # Plots a zoomed detail axins = zoomed_inset_axes(ax, zoom=3, loc='lower left') axins.imshow(-background_data, cmap='Greys', vmin=-1.0, vmax=0.0, alpha=MAP_ALPHA) rand_idx = np.random.randint(low=0, high=y_true.shape[0], size=1) axins.scatter( x=y_pred[rand_idx, 0, :], y=y_pred[rand_idx, 1, :], c='b', s=4, alpha=0.15, edgecolors='none', ) axins.scatter(x=y_true[rand_idx, 0], y=y_true[rand_idx, 1], c='r', edgecolors='none') # Limit the region for zoom axins.set_xlim(y_true[rand_idx, 0] - ZOOM_SIZE, y_true[rand_idx, 0] + ZOOM_SIZE) axins.set_ylim(y_true[rand_idx, 1] - ZOOM_SIZE, y_true[rand_idx, 1] + ZOOM_SIZE) # Hides ticks plt.xticks(visible=False) plt.yticks(visible=False) # draw a bbox of the region of the inset axes in the parent axes and connecting lines between # the bbox and the inset axes area. Also inverts the y of the inner plot, as the outer plot # is inverted (imshow) axins.invert_yaxis() # ================================================================ # (adapted from `mark_inset`, removing the connectors) parent_axes = ax inset_axes = axins kwargs = {"fc": "none", "ec": "0.0"} rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData) fill = bool({'fc', 'facecolor', 'color'}.intersection(kwargs)) pp = BboxPatch(rect, fill=fill, **kwargs) parent_axes.add_patch(pp) # ================================================================ plt.draw() plt.savefig(PLOT_PATH, dpi=300) logging.info("Plot written to %s", PLOT_PATH)
def auto_adjust_subplotpars(fig, renderer, nrows_ncols, num1num2_list, subplot_list, ax_bbox_list=None, pad=1.08, h_pad=None, w_pad=None, rect=None): """ Return a dict of subplot parameters to adjust spacing between subplots or ``None`` if resulting axes would have zero height or width. Note that this function ignores geometry information of subplot itself, but uses what is given by the *nrows_ncols* and *num1num2_list* parameters. Also, the results could be incorrect if some subplots have ``adjustable=datalim``. Parameters ---------- nrows_ncols : Tuple[int, int] Number of rows and number of columns of the grid. num1num2_list : List[int] List of numbers specifying the area occupied by the subplot subplot_list : list of subplots List of subplots that will be used to calculate optimal subplot_params. pad : float Padding between the figure edge and the edges of subplots, as a fraction of the font size. h_pad, w_pad : float Padding (height/width) between edges of adjacent subplots, as a fraction of the font size. Defaults to *pad*. rect : Tuple[float, float, float, float] [left, bottom, right, top] in normalized (0, 1) figure coordinates. """ rows, cols = nrows_ncols font_size_inches = ( FontProperties(size=rcParams["font.size"]).get_size_in_points() / 72) pad_inches = pad * font_size_inches vpad_inches = h_pad * font_size_inches if h_pad is not None else pad_inches hpad_inches = w_pad * font_size_inches if w_pad is not None else pad_inches if len(num1num2_list) != len(subplot_list) or len(subplot_list) == 0: raise ValueError if rect is None: margin_left = margin_bottom = margin_right = margin_top = None else: margin_left, margin_bottom, _right, _top = rect margin_right = 1 - _right if _right else None margin_top = 1 - _top if _top else None vspaces = np.zeros((rows + 1, cols)) hspaces = np.zeros((rows, cols + 1)) if ax_bbox_list is None: ax_bbox_list = [ Bbox.union([ax.get_position(original=True) for ax in subplots]) for subplots in subplot_list ] for subplots, ax_bbox, (num1, num2) in zip(subplot_list, ax_bbox_list, num1num2_list): if all(not ax.get_visible() for ax in subplots): continue bb = [] for ax in subplots: if ax.get_visible(): try: bb += [ax.get_tightbbox(renderer, for_layout_only=True)] except TypeError: bb += [ax.get_tightbbox(renderer)] tight_bbox_raw = Bbox.union(bb) tight_bbox = TransformedBbox(tight_bbox_raw, fig.transFigure.inverted()) row1, col1 = divmod(num1, cols) if num2 is None: num2 = num1 row2, col2 = divmod(num2, cols) for row_i in range(row1, row2 + 1): hspaces[row_i, col1] += ax_bbox.xmin - tight_bbox.xmin # left hspaces[row_i, col2 + 1] += tight_bbox.xmax - ax_bbox.xmax # right for col_i in range(col1, col2 + 1): vspaces[row1, col_i] += tight_bbox.ymax - ax_bbox.ymax # top vspaces[row2 + 1, col_i] += ax_bbox.ymin - tight_bbox.ymin # bot. fig_width_inch, fig_height_inch = fig.get_size_inches() # margins can be negative for axes with aspect applied, so use max(, 0) to # make them nonnegative. if not margin_left: margin_left = (max(hspaces[:, 0].max(), 0) + pad_inches / fig_width_inch) if not margin_right: margin_right = (max(hspaces[:, -1].max(), 0) + pad_inches / fig_width_inch) if not margin_top: margin_top = (max(vspaces[0, :].max(), 0) + pad_inches / fig_height_inch) suptitle = fig._suptitle if suptitle and suptitle.get_in_layout(): rel_suptitle_height = fig.transFigure.inverted().transform_bbox( suptitle.get_window_extent(renderer)).height margin_top += rel_suptitle_height + pad_inches / fig_height_inch if not margin_bottom: margin_bottom = (max(vspaces[-1, :].max(), 0) + pad_inches / fig_height_inch) if margin_left + margin_right >= 1: cbook._warn_external('Tight layout not applied. The left and right ' 'margins cannot be made large enough to ' 'accommodate all axes decorations. ') return None if margin_bottom + margin_top >= 1: cbook._warn_external('Tight layout not applied. The bottom and top ' 'margins cannot be made large enough to ' 'accommodate all axes decorations. ') return None kwargs = dict(left=margin_left, right=1 - margin_right, bottom=margin_bottom, top=1 - margin_top) if cols > 1: hspace = hspaces[:, 1:-1].max() + hpad_inches / fig_width_inch # axes widths: h_axes = (1 - margin_right - margin_left - hspace * (cols - 1)) / cols if h_axes < 0: cbook._warn_external('Tight layout not applied. tight_layout ' 'cannot make axes width small enough to ' 'accommodate all axes decorations') return None else: kwargs["wspace"] = hspace / h_axes if rows > 1: vspace = vspaces[1:-1, :].max() + vpad_inches / fig_height_inch v_axes = (1 - margin_top - margin_bottom - vspace * (rows - 1)) / rows if v_axes < 0: cbook._warn_external('Tight layout not applied. tight_layout ' 'cannot make axes height small enough to ' 'accommodate all axes decorations') return None else: kwargs["hspace"] = vspace / v_axes return kwargs
def auto_adjust_subplotpars(fig, renderer, nrows_ncols, num1num2_list, subplot_list, ax_bbox_list=None, pad=1.08, h_pad=None, w_pad=None, rect=None): """ Return a dict of subplot parameters to adjust spacing between subplots or ``None`` if resulting axes would have zero height or width. Note that this function ignores geometry information of subplot itself, but uses what is given by the *nrows_ncols* and *num1num2_list* parameters. Also, the results could be incorrect if some subplots have ``adjustable=datalim``. Parameters ---------- nrows_ncols : Tuple[int, int] Number of rows and number of columns of the grid. num1num2_list : List[int] List of numbers specifying the area occupied by the subplot subplot_list : list of subplots List of subplots that will be used to calculate optimal subplot_params. pad : float Padding between the figure edge and the edges of subplots, as a fraction of the font size. h_pad, w_pad : float Padding (height/width) between edges of adjacent subplots, as a fraction of the font size. Defaults to *pad*. rect : Tuple[float, float, float, float] [left, bottom, right, top] in normalized (0, 1) figure coordinates. """ rows, cols = nrows_ncols font_size_inches = ( FontProperties(size=rcParams["font.size"]).get_size_in_points() / 72) pad_inches = pad * font_size_inches if h_pad is not None: vpad_inches = h_pad * font_size_inches else: vpad_inches = pad_inches if w_pad is not None: hpad_inches = w_pad * font_size_inches else: hpad_inches = pad_inches if len(num1num2_list) != len(subplot_list) or len(subplot_list) == 0: raise ValueError if rect is None: margin_left = margin_bottom = margin_right = margin_top = None else: margin_left, margin_bottom, _right, _top = rect if _right: margin_right = 1 - _right else: margin_right = None if _top: margin_top = 1 - _top else: margin_top = None vspaces = [[] for i in range((rows + 1) * cols)] hspaces = [[] for i in range(rows * (cols + 1))] union = Bbox.union if ax_bbox_list is None: ax_bbox_list = [] for subplots in subplot_list: ax_bbox = union( [ax.get_position(original=True) for ax in subplots]) ax_bbox_list.append(ax_bbox) for subplots, ax_bbox, (num1, num2) in zip(subplot_list, ax_bbox_list, num1num2_list): if all(not ax.get_visible() for ax in subplots): continue tight_bbox_raw = union([ ax.get_tightbbox(renderer) for ax in subplots if ax.get_visible() ]) tight_bbox = TransformedBbox(tight_bbox_raw, fig.transFigure.inverted()) row1, col1 = divmod(num1, cols) if num2 is None: # left hspaces[row1 * (cols + 1) + col1].append( _get_left(tight_bbox, ax_bbox)) # right hspaces[row1 * (cols + 1) + (col1 + 1)].append( _get_right(tight_bbox, ax_bbox)) # top vspaces[row1 * cols + col1].append(_get_top(tight_bbox, ax_bbox)) # bottom vspaces[(row1 + 1) * cols + col1].append( _get_bottom(tight_bbox, ax_bbox)) else: row2, col2 = divmod(num2, cols) for row_i in range(row1, row2 + 1): # left hspaces[row_i * (cols + 1) + col1].append( _get_left(tight_bbox, ax_bbox)) # right hspaces[row_i * (cols + 1) + (col2 + 1)].append( _get_right(tight_bbox, ax_bbox)) for col_i in range(col1, col2 + 1): # top vspaces[row1 * cols + col_i].append( _get_top(tight_bbox, ax_bbox)) # bottom vspaces[(row2 + 1) * cols + col_i].append( _get_bottom(tight_bbox, ax_bbox)) fig_width_inch, fig_height_inch = fig.get_size_inches() # margins can be negative for axes with aspect applied. And we # append + [0] to make minimum margins 0 if not margin_left: margin_left = max([sum(s) for s in hspaces[::cols + 1]] + [0]) margin_left += pad_inches / fig_width_inch if not margin_right: margin_right = max([sum(s) for s in hspaces[cols::cols + 1]] + [0]) margin_right += pad_inches / fig_width_inch if not margin_top: margin_top = max([sum(s) for s in vspaces[:cols]] + [0]) margin_top += pad_inches / fig_height_inch if not margin_bottom: margin_bottom = max([sum(s) for s in vspaces[-cols:]] + [0]) margin_bottom += pad_inches / fig_height_inch if margin_left + margin_right >= 1: warnings.warn('Tight layout not applied. The left and right margins ' 'cannot be made large ' 'enough to accommodate all axes decorations. ') return None if margin_bottom + margin_top >= 1: warnings.warn('Tight layout not applied. ' 'The bottom and top margins cannot be made large ' 'enough to accommodate all axes decorations. ') return None kwargs = dict(left=margin_left, right=1 - margin_right, bottom=margin_bottom, top=1 - margin_top) if cols > 1: hspace = (max( sum(s) for i in range(rows) for s in hspaces[i * (cols + 1) + 1:(i + 1) * (cols + 1) - 1]) + hpad_inches / fig_width_inch) # axes widths: h_axes = (1 - margin_right - margin_left - hspace * (cols - 1)) / cols if h_axes < 0: warnings.warn('Tight layout not applied. ' 'tight_layout cannot make axes width small enough ' 'to accommodate all axes decorations') return None else: kwargs["wspace"] = hspace / h_axes if rows > 1: vspace = (max(sum(s) for s in vspaces[cols:-cols]) + vpad_inches / fig_height_inch) v_axes = (1 - margin_top - margin_bottom - vspace * (rows - 1)) / rows if v_axes < 0: warnings.warn('Tight layout not applied. ' 'tight_layout cannot make axes height small enough ' 'to accommodate all axes decorations') return None else: kwargs["hspace"] = vspace / v_axes return kwargs
def auto_adjust_subplotpars(fig, renderer, nrows_ncols, num1num2_list, subplot_list, ax_bbox_list=None, pad=1.2, h_pad=None, w_pad=None, rect=None): """ Return a dictionary of subplot parameters so that spacing between subplots are adjusted. Note that this function ignore geometry information of subplot itself, but uses what is given by *nrows_ncols* and *num1num2_list* parameteres. Also, the results could be incorrect if some subplots have ``adjustable=datalim``. Parameters: nrows_ncols number of rows and number of columns of the grid. num1num2_list list of numbers specifying the area occupied by the subplot subplot_list list of subplots that will be used to calcuate optimal subplot_params. pad : float padding between the figure edge and the edges of subplots, as a fraction of the font-size. h_pad, w_pad : float padding (height/width) between edges of adjacent subplots. Defaults to `pad_inches`. rect [left, bottom, right, top] in normalized (0, 1) figure coordinates. """ rows, cols = nrows_ncols pad_inches = pad * FontProperties( size=rcParams["font.size"]).get_size_in_points() / renderer.dpi if h_pad is not None: vpad_inches = h_pad * FontProperties( size=rcParams["font.size"]).get_size_in_points() / renderer.dpi else: vpad_inches = pad_inches if w_pad is not None: hpad_inches = w_pad * FontProperties( size=rcParams["font.size"]).get_size_in_points() / renderer.dpi else: hpad_inches = pad_inches if len(subplot_list) == 0: raise RuntimeError("") if len(num1num2_list) != len(subplot_list): raise RuntimeError("") if rect is None: margin_left, margin_bottom, margin_right, margin_top = None, None, None, None else: margin_left, margin_bottom, _right, _top = rect if _right: margin_right = 1. - _right else: margin_right = None if _top: margin_top = 1. - _top else: margin_top = None vspaces = [[] for i in range((rows + 1) * cols)] hspaces = [[] for i in range(rows * (cols + 1))] union = Bbox.union if ax_bbox_list is None: ax_bbox_list = [] for subplots in subplot_list: ax_bbox = union( [ax.get_position(original=True) for ax in subplots]) ax_bbox_list.append(ax_bbox) for subplots, ax_bbox, (num1, num2) in zip(subplot_list, ax_bbox_list, num1num2_list): #ax_bbox = union([ax.get_position(original=True) for ax in subplots]) tight_bbox_raw = union([ax.get_tightbbox(renderer) for ax in subplots]) tight_bbox = TransformedBbox(tight_bbox_raw, fig.transFigure.inverted()) row1, col1 = divmod(num1, cols) if num2 is None: # left hspaces[row1 * (cols + 1) + col1].append( _get_left(tight_bbox, ax_bbox)) # right hspaces[row1 * (cols + 1) + (col1 + 1)].append( _get_right(tight_bbox, ax_bbox)) # top vspaces[row1 * cols + col1].append(_get_top(tight_bbox, ax_bbox)) # bottom vspaces[(row1 + 1) * cols + col1].append( _get_bottom(tight_bbox, ax_bbox)) else: row2, col2 = divmod(num2, cols) for row_i in range(row1, row2 + 1): # left hspaces[row_i * (cols + 1) + col1].append( _get_left(tight_bbox, ax_bbox)) # right hspaces[row_i * (cols + 1) + (col2 + 1)].append( _get_right(tight_bbox, ax_bbox)) for col_i in range(col1, col2 + 1): # top vspaces[row1 * cols + col_i].append( _get_top(tight_bbox, ax_bbox)) # bottom vspaces[(row2 + 1) * cols + col_i].append( _get_bottom(tight_bbox, ax_bbox)) fig_width_inch, fig_height_inch = fig.get_size_inches() # margins can be negative for axes with aspect applied. And we # append + [0] to make minimum margins 0 if not margin_left: margin_left = max([sum(s) for s in hspaces[::cols + 1]] + [0]) margin_left += pad_inches / fig_width_inch if not margin_right: margin_right = max([sum(s) for s in hspaces[cols::cols + 1]] + [0]) margin_right += pad_inches / fig_width_inch if not margin_top: margin_top = max([sum(s) for s in vspaces[:cols]] + [0]) margin_top += pad_inches / fig_height_inch if not margin_bottom: margin_bottom = max([sum(s) for s in vspaces[-cols:]] + [0]) margin_bottom += pad_inches / fig_height_inch kwargs = dict(left=margin_left, right=1 - margin_right, bottom=margin_bottom, top=1 - margin_top) if cols > 1: hspace = max([ sum(s) for i in range(rows) for s in hspaces[i * (cols + 1) + 1:(i + 1) * (cols + 1) - 1] ]) hspace += hpad_inches / fig_width_inch h_axes = ((1 - margin_right - margin_left) - hspace * (cols - 1)) / cols kwargs["wspace"] = hspace / h_axes if rows > 1: vspace = max([sum(s) for s in vspaces[cols:-cols]]) vspace += vpad_inches / fig_height_inch v_axes = ((1 - margin_top - margin_bottom) - vspace * (rows - 1)) / rows kwargs["hspace"] = vspace / v_axes return kwargs
def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, unsampled=False, round_to_pixel_border=True): """ Normalize, rescale and color the image `A` from the given in_bbox (in data space), to the given out_bbox (in pixel space) clipped to the given clip_bbox (also in pixel space), and magnified by the magnification factor. `A` may be a greyscale image (MxN) with a dtype of `float32`, `float64`, `uint16` or `uint8`, or an RGBA image (MxNx4) with a dtype of `float32`, `float64`, or `uint8`. If `unsampled` is True, the image will not be scaled, but an appropriate affine transformation will be returned instead. If `round_to_pixel_border` is True, the output image size will be rounded to the nearest pixel boundary. This makes the images align correctly with the axes. It should not be used in cases where you want exact scaling, however, such as FigureImage. Returns the resulting (image, x, y, trans), where (x, y) is the upper left corner of the result in pixel space, and `trans` is the affine transformation from the image to pixel space. """ if A is None: raise RuntimeError('You must first set the image' ' array or the image attribute') clipped_bbox = Bbox.intersection(out_bbox, clip_bbox) if clipped_bbox is None: return None, 0, 0, None out_width_base = clipped_bbox.width * magnification out_height_base = clipped_bbox.height * magnification if out_width_base == 0 or out_height_base == 0: return None, 0, 0, None if self.origin == 'upper': # Flip the input image using a transform. This avoids the # problem with flipping the array, which results in a copy # when it is converted to contiguous in the C wrapper t0 = Affine2D().translate(0, -A.shape[0]).scale(1, -1) else: t0 = IdentityTransform() t0 += ( Affine2D() .scale( in_bbox.width / A.shape[1], in_bbox.height / A.shape[0]) .translate(in_bbox.x0, in_bbox.y0) + self.get_transform()) t = (t0 + Affine2D().translate( -clipped_bbox.x0, -clipped_bbox.y0) .scale(magnification, magnification)) # So that the image is aligned with the edge of the axes, we want # to round up the output width to the next integer. This also # means scaling the transform just slightly to account for the # extra subpixel. if (t.is_affine and round_to_pixel_border and (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)): out_width = int(ceil(out_width_base) + 1) out_height = int(ceil(out_height_base) + 1) extra_width = (out_width - out_width_base) / out_width_base extra_height = (out_height - out_height_base) / out_height_base t += Affine2D().scale( 1.0 + extra_width, 1.0 + extra_height) else: out_width = int(out_width_base) out_height = int(out_height_base) if not unsampled: if A.ndim == 2: A = self.norm(A) if A.dtype.kind == 'f': # For floating-point greyscale images, we treat negative # numbers as transparent. # TODO: Use np.full when we support Numpy 1.9 as a # minimum output = np.empty((out_height, out_width), dtype=A.dtype) output[...] = -100.0 else: output = np.zeros((out_height, out_width), dtype=A.dtype) alpha = 1.0 elif A.ndim == 3: # Always convert to RGBA, even if only RGB input if A.shape[2] == 3: A = _rgb_to_rgba(A) elif A.shape[2] != 4: raise ValueError("Invalid dimensions, got %s" % (A.shape,)) output = np.zeros((out_height, out_width, 4), dtype=A.dtype) alpha = self.get_alpha() if alpha is None: alpha = 1.0 else: raise ValueError("Invalid dimensions, got %s" % (A.shape,)) _image.resample( A, output, t, _interpd_[self.get_interpolation()], self.get_resample(), alpha, self.get_filternorm() or 0.0, self.get_filterrad() or 0.0) output = self.to_rgba(output, bytes=True, norm=False) # Apply alpha *after* if the input was greyscale if A.ndim == 2: alpha = self.get_alpha() if alpha is not None and alpha != 1.0: alpha_channel = output[:, :, 3] alpha_channel[:] = np.asarray( np.asarray(alpha_channel, np.float32) * alpha, np.uint8) else: if self._imcache is None: self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2)) output = self._imcache # Subset the input image to only the part that will be # displayed subset = TransformedBbox( clip_bbox, t0.frozen().inverted()).frozen() output = output[ int(max(subset.ymin, 0)): int(min(subset.ymax + 1, output.shape[0])), int(max(subset.xmin, 0)): int(min(subset.xmax + 1, output.shape[1]))] t = Affine2D().translate( int(max(subset.xmin, 0)), int(max(subset.ymin, 0))) + t return output, clipped_bbox.x0, clipped_bbox.y0, t
def test_fill_facecolor(): fig, ax = plt.subplots(1, 5) fig.set_size_inches(5, 5) for i in range(1, 4): ax[i].yaxis.set_visible(False) ax[4].yaxis.tick_right() bbox = Bbox.from_extents(0, 0.4, 1, 0.6) # fill with blue by setting 'fc' field bbox1 = TransformedBbox(bbox, ax[0].transData) bbox2 = TransformedBbox(bbox, ax[1].transData) # set color to BboxConnectorPatch p = BboxConnectorPatch( bbox1, bbox2, loc1a=1, loc2a=2, loc1b=4, loc2b=3, ec="r", fc="b") p.set_clip_on(False) ax[0].add_patch(p) # set color to marked area axins = zoomed_inset_axes(ax[0], 1, loc='upper right') axins.set_xlim(0, 0.2) axins.set_ylim(0, 0.2) plt.gca().axes.xaxis.set_ticks([]) plt.gca().axes.yaxis.set_ticks([]) mark_inset(ax[0], axins, loc1=2, loc2=4, fc="b", ec="0.5") # fill with yellow by setting 'facecolor' field bbox3 = TransformedBbox(bbox, ax[1].transData) bbox4 = TransformedBbox(bbox, ax[2].transData) # set color to BboxConnectorPatch p = BboxConnectorPatch( bbox3, bbox4, loc1a=1, loc2a=2, loc1b=4, loc2b=3, ec="r", facecolor="y") p.set_clip_on(False) ax[1].add_patch(p) # set color to marked area axins = zoomed_inset_axes(ax[1], 1, loc='upper right') axins.set_xlim(0, 0.2) axins.set_ylim(0, 0.2) plt.gca().axes.xaxis.set_ticks([]) plt.gca().axes.yaxis.set_ticks([]) mark_inset(ax[1], axins, loc1=2, loc2=4, facecolor="y", ec="0.5") # fill with green by setting 'color' field bbox5 = TransformedBbox(bbox, ax[2].transData) bbox6 = TransformedBbox(bbox, ax[3].transData) # set color to BboxConnectorPatch p = BboxConnectorPatch( bbox5, bbox6, loc1a=1, loc2a=2, loc1b=4, loc2b=3, ec="r", color="g") p.set_clip_on(False) ax[2].add_patch(p) # set color to marked area axins = zoomed_inset_axes(ax[2], 1, loc='upper right') axins.set_xlim(0, 0.2) axins.set_ylim(0, 0.2) plt.gca().axes.xaxis.set_ticks([]) plt.gca().axes.yaxis.set_ticks([]) mark_inset(ax[2], axins, loc1=2, loc2=4, color="g", ec="0.5") # fill with green but color won't show if set fill to False bbox7 = TransformedBbox(bbox, ax[3].transData) bbox8 = TransformedBbox(bbox, ax[4].transData) # BboxConnectorPatch won't show green p = BboxConnectorPatch( bbox7, bbox8, loc1a=1, loc2a=2, loc1b=4, loc2b=3, ec="r", fc="g", fill=False) p.set_clip_on(False) ax[3].add_patch(p) # marked area won't show green axins = zoomed_inset_axes(ax[3], 1, loc='upper right') axins.set_xlim(0, 0.2) axins.set_ylim(0, 0.2) axins.xaxis.set_ticks([]) axins.yaxis.set_ticks([]) mark_inset(ax[3], axins, loc1=2, loc2=4, fc="g", ec="0.5", fill=False)
def __init__(self, figsize = None, # defaults to rc figure.figsize dpi = None, # defaults to rc figure.dpi facecolor = None, # defaults to rc figure.facecolor edgecolor = None, # defaults to rc figure.edgecolor linewidth = 0.0, # the default linewidth of the frame frameon = True, # whether or not to draw the figure frame subplotpars = None, # default to rc tight_layout = None, # default to rc figure.autolayout ): """ *figsize* w,h tuple in inches *dpi* Dots per inch *facecolor* The figure patch facecolor; defaults to rc ``figure.facecolor`` *edgecolor* The figure patch edge color; defaults to rc ``figure.edgecolor`` *linewidth* The figure patch edge linewidth; the default linewidth of the frame *frameon* If *False*, suppress drawing the figure frame *subplotpars* A :class:`SubplotParams` instance, defaults to rc *tight_layout* If *False* use *subplotpars*; if *True* adjust subplot parameters using :meth:`tight_layout`. Defaults to rc ``figure.autolayout``. """ Artist.__init__(self) self.callbacks = cbook.CallbackRegistry() if figsize is None : figsize = rcParams['figure.figsize'] if dpi is None : dpi = rcParams['figure.dpi'] if facecolor is None: facecolor = rcParams['figure.facecolor'] if edgecolor is None: edgecolor = rcParams['figure.edgecolor'] self.dpi_scale_trans = Affine2D() self.dpi = dpi self.bbox_inches = Bbox.from_bounds(0, 0, *figsize) self.bbox = TransformedBbox(self.bbox_inches, self.dpi_scale_trans) self.frameon = frameon self.transFigure = BboxTransformTo(self.bbox) # the figurePatch name is deprecated self.patch = self.figurePatch = Rectangle( xy=(0,0), width=1, height=1, facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, ) self._set_artist_props(self.patch) self.patch.set_aa(False) self._hold = rcParams['axes.hold'] self.canvas = None if subplotpars is None: subplotpars = SubplotParams() self.subplotpars = subplotpars self.set_tight_layout(tight_layout) self._axstack = AxesStack() # track all figure axes and current axes self.clf() self._cachedRenderer = None
def __call__(self, ax, renderer): bbox_parent = self.parent.get_position(original=False) trans = BboxTransformTo(bbox_parent) bbox_inset = Bbox.from_bounds(*self.lbwh) bb = TransformedBbox(bbox_inset, trans) return bb
def adjust_bbox(fig, bbox_inches, fixed_dpi=None): """ Temporarily adjust the figure so that only the specified area (bbox_inches) is saved. It modifies figure.bbox, figure.bbox_inches, figure.transFigure._boxout, and figure.patch. While the figure size changes, the scale of the original figure is conserved. A function which restores the original values are returned. """ origBbox = fig.bbox origBboxInches = fig.bbox_inches _boxout = fig.transFigure._boxout asp_list = [] locator_list = [] for ax in fig.axes: pos = ax.get_position(original=False).frozen() locator_list.append(ax.get_axes_locator()) asp_list.append(ax.get_aspect()) def _l(a, r, pos=pos): return pos ax.set_axes_locator(_l) ax.set_aspect("auto") def restore_bbox(): for ax, asp, loc in zip(fig.axes, asp_list, locator_list): ax.set_aspect(asp) ax.set_axes_locator(loc) fig.bbox = origBbox fig.bbox_inches = origBboxInches fig.transFigure._boxout = _boxout fig.transFigure.invalidate() fig.patch.set_bounds(0, 0, 1, 1) if fixed_dpi is not None: tr = Affine2D().scale(fixed_dpi) dpi_scale = fixed_dpi / fig.dpi else: tr = Affine2D().scale(fig.dpi) dpi_scale = 1. _bbox = TransformedBbox(bbox_inches, tr) fig.bbox_inches = Bbox.from_bounds(0, 0, bbox_inches.width, bbox_inches.height) x0, y0 = _bbox.x0, _bbox.y0 w1, h1 = fig.bbox.width * dpi_scale, fig.bbox.height * dpi_scale fig.transFigure._boxout = Bbox.from_bounds(-x0, -y0, w1, h1) fig.transFigure.invalidate() fig.bbox = TransformedBbox(fig.bbox_inches, tr) fig.patch.set_bounds(x0 / w1, y0 / h1, fig.bbox.width / w1, fig.bbox.height / h1) return restore_bbox
years = np.arange(2004, 2009) box_colors = [ (0.8, 0.2, 0.2), (0.2, 0.8, 0.2), (0.2, 0.2, 0.8), (0.7, 0.5, 0.8), (0.3, 0.8, 0.7), ] heights = np.random.random(years.shape) * 7000 + 3000 fmt = ScalarFormatter(useOffset=False) ax.xaxis.set_major_formatter(fmt) for year, h, bc in zip(years, heights, box_colors): bbox0 = Bbox.from_extents(year - 0.4, 0., year + 0.4, h) bbox = TransformedBbox(bbox0, ax.transData) rb_patch = RibbonBoxImage(bbox, bc, interpolation="bicubic") ax.add_artist(rb_patch) ax.annotate(r"%d" % (int(h / 100.) * 100), (year, h), va="bottom", ha="center") patch_gradient = BboxImage( ax.bbox, interpolation="bicubic", zorder=0.1, ) gradient = np.zeros((2, 2, 4), dtype=float) gradient[:, :, :3] = [1, 1, 0.]
def _make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification=1.0, unsampled=False, round_to_pixel_border=True): """ Normalize, rescale and color the image `A` from the given in_bbox (in data space), to the given out_bbox (in pixel space) clipped to the given clip_bbox (also in pixel space), and magnified by the magnification factor. `A` may be a greyscale image (MxN) with a dtype of `float32`, `float64`, `uint16` or `uint8`, or an RGBA image (MxNx4) with a dtype of `float32`, `float64`, or `uint8`. If `unsampled` is True, the image will not be scaled, but an appropriate affine transformation will be returned instead. If `round_to_pixel_border` is True, the output image size will be rounded to the nearest pixel boundary. This makes the images align correctly with the axes. It should not be used in cases where you want exact scaling, however, such as FigureImage. Returns the resulting (image, x, y, trans), where (x, y) is the upper left corner of the result in pixel space, and `trans` is the affine transformation from the image to pixel space. """ if A is None: raise RuntimeError('You must first set the image' ' array or the image attribute') clipped_bbox = Bbox.intersection(out_bbox, clip_bbox) if clipped_bbox is None: return None, 0, 0, None out_width_base = clipped_bbox.width * magnification out_height_base = clipped_bbox.height * magnification if out_width_base == 0 or out_height_base == 0: return None, 0, 0, None if self.origin == 'upper': # Flip the input image using a transform. This avoids the # problem with flipping the array, which results in a copy # when it is converted to contiguous in the C wrapper t0 = Affine2D().translate(0, -A.shape[0]).scale(1, -1) else: t0 = IdentityTransform() t0 += ( Affine2D() .scale( in_bbox.width / A.shape[1], in_bbox.height / A.shape[0]) .translate(in_bbox.x0, in_bbox.y0) + self.get_transform()) t = (t0 + Affine2D().translate( -clipped_bbox.x0, -clipped_bbox.y0) .scale(magnification, magnification)) # So that the image is aligned with the edge of the axes, we want # to round up the output width to the next integer. This also # means scaling the transform just slightly to account for the # extra subpixel. if (t.is_affine and round_to_pixel_border and (out_width_base % 1.0 != 0.0 or out_height_base % 1.0 != 0.0)): out_width = int(ceil(out_width_base)) out_height = int(ceil(out_height_base)) extra_width = (out_width - out_width_base) / out_width_base extra_height = (out_height - out_height_base) / out_height_base t += Affine2D().scale( 1.0 + extra_width, 1.0 + extra_height) else: out_width = int(out_width_base) out_height = int(out_height_base) if not unsampled: created_rgba_mask = False if A.ndim not in (2, 3): raise ValueError("Invalid dimensions, got %s" % (A.shape,)) if A.ndim == 2: A = self.norm(A) if A.dtype.kind == 'f': # If the image is greyscale, convert to RGBA and # use the extra channels for resizing the over, # under, and bad pixels. This is needed because # Agg's resampler is very aggressive about # clipping to [0, 1] and we use out-of-bounds # values to carry the over/under/bad information rgba = np.empty((A.shape[0], A.shape[1], 4), dtype=A.dtype) rgba[..., 0] = A # normalized data rgba[..., 1] = A < 0 # under data rgba[..., 2] = A > 1 # over data rgba[..., 3] = ~A.mask # bad data A = rgba output = np.zeros((out_height, out_width, 4), dtype=A.dtype) alpha = 1.0 created_rgba_mask = True else: # colormap norms that output integers (ex NoNorm # and BoundaryNorm) to RGBA space before # interpolating. This is needed due to the # Agg resampler only working on floats in the # range [0, 1] and because interpolating indexes # into an arbitrary LUT may be problematic. # # This falls back to interpolating in RGBA space which # can produce it's own artifacts of colors not in the map # showing up in the final image. A = self.cmap(A, alpha=self.get_alpha(), bytes=True) if not created_rgba_mask: # Always convert to RGBA, even if only RGB input if A.shape[2] == 3: A = _rgb_to_rgba(A) elif A.shape[2] != 4: raise ValueError("Invalid dimensions, got %s" % (A.shape,)) output = np.zeros((out_height, out_width, 4), dtype=A.dtype) alpha = self.get_alpha() if alpha is None: alpha = 1.0 _image.resample( A, output, t, _interpd_[self.get_interpolation()], self.get_resample(), alpha, self.get_filternorm() or 0.0, self.get_filterrad() or 0.0) if created_rgba_mask: # Convert back to a masked greyscale array so # colormapping works correctly hid_output = output output = np.ma.masked_array( hid_output[..., 0], hid_output[..., 3] < 0.5) # relabel under data output[hid_output[..., 1] > .5] = -1 # relabel over data output[hid_output[..., 2] > .5] = 2 output = self.to_rgba(output, bytes=True, norm=False) # Apply alpha *after* if the input was greyscale without a mask if A.ndim == 2 or created_rgba_mask: alpha = self.get_alpha() if alpha is not None and alpha != 1.0: alpha_channel = output[:, :, 3] alpha_channel[:] = np.asarray( np.asarray(alpha_channel, np.float32) * alpha, np.uint8) else: if self._imcache is None: self._imcache = self.to_rgba(A, bytes=True, norm=(A.ndim == 2)) output = self._imcache # Subset the input image to only the part that will be # displayed subset = TransformedBbox( clip_bbox, t0.frozen().inverted()).frozen() output = output[ int(max(subset.ymin, 0)): int(min(subset.ymax + 1, output.shape[0])), int(max(subset.xmin, 0)): int(min(subset.xmax + 1, output.shape[1]))] t = Affine2D().translate( int(max(subset.xmin, 0)), int(max(subset.ymin, 0))) + t return output, clipped_bbox.x0, clipped_bbox.y0, t
class Figure(Artist): """ The Figure instance supports callbacks through a *callbacks* attribute which is a :class:`matplotlib.cbook.CallbackRegistry` instance. The events you can connect to are 'dpi_changed', and the callback will be called with ``func(fig)`` where fig is the :class:`Figure` instance. *patch* The figure patch is drawn by a :class:`matplotlib.patches.Rectangle` instance *suppressComposite* For multiple figure images, the figure will make composite images depending on the renderer option_image_nocomposite function. If suppressComposite is True|False, this will override the renderer. """ def __str__(self): return "Figure(%gx%g)" % tuple(self.bbox.size) def __init__(self, figsize = None, # defaults to rc figure.figsize dpi = None, # defaults to rc figure.dpi facecolor = None, # defaults to rc figure.facecolor edgecolor = None, # defaults to rc figure.edgecolor linewidth = 0.0, # the default linewidth of the frame frameon = True, # whether or not to draw the figure frame subplotpars = None, # default to rc tight_layout = None, # default to rc figure.autolayout ): """ *figsize* w,h tuple in inches *dpi* Dots per inch *facecolor* The figure patch facecolor; defaults to rc ``figure.facecolor`` *edgecolor* The figure patch edge color; defaults to rc ``figure.edgecolor`` *linewidth* The figure patch edge linewidth; the default linewidth of the frame *frameon* If *False*, suppress drawing the figure frame *subplotpars* A :class:`SubplotParams` instance, defaults to rc *tight_layout* If *False* use *subplotpars*; if *True* adjust subplot parameters using :meth:`tight_layout`. Defaults to rc ``figure.autolayout``. """ Artist.__init__(self) self.callbacks = cbook.CallbackRegistry() if figsize is None : figsize = rcParams['figure.figsize'] if dpi is None : dpi = rcParams['figure.dpi'] if facecolor is None: facecolor = rcParams['figure.facecolor'] if edgecolor is None: edgecolor = rcParams['figure.edgecolor'] self.dpi_scale_trans = Affine2D() self.dpi = dpi self.bbox_inches = Bbox.from_bounds(0, 0, *figsize) self.bbox = TransformedBbox(self.bbox_inches, self.dpi_scale_trans) self.frameon = frameon self.transFigure = BboxTransformTo(self.bbox) # the figurePatch name is deprecated self.patch = self.figurePatch = Rectangle( xy=(0,0), width=1, height=1, facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, ) self._set_artist_props(self.patch) self.patch.set_aa(False) self._hold = rcParams['axes.hold'] self.canvas = None if subplotpars is None: subplotpars = SubplotParams() self.subplotpars = subplotpars self.set_tight_layout(tight_layout) self._axstack = AxesStack() # track all figure axes and current axes self.clf() self._cachedRenderer = None def _get_axes(self): return self._axstack.as_list() axes = property(fget=_get_axes, doc="Read-only: list of axes in Figure") def _get_dpi(self): return self._dpi def _set_dpi(self, dpi): self._dpi = dpi self.dpi_scale_trans.clear().scale(dpi, dpi) self.callbacks.process('dpi_changed', self) dpi = property(_get_dpi, _set_dpi) def get_tight_layout(self): """ Return the Boolean flag, True to use :meth`tight_layout` when drawing. """ return self._tight def set_tight_layout(self, tight): """ Set whether :meth:`tight_layout` is used upon drawing. If None, the rcParams['figure.autolayout'] value will be set. ACCEPTS: [True | False | None] """ if tight is None: tight = rcParams['figure.autolayout'] tight = bool(tight) self._tight = tight def autofmt_xdate(self, bottom=0.2, rotation=30, ha='right'): """ Date ticklabels often overlap, so it is useful to rotate them and right align them. Also, a common use case is a number of subplots with shared xaxes where the x-axis is date data. The ticklabels are often long, and it helps to rotate them on the bottom subplot and turn them off on other subplots, as well as turn off xlabels. *bottom* The bottom of the subplots for :meth:`subplots_adjust` *rotation* The rotation of the xtick labels *ha* The horizontal alignment of the xticklabels """ allsubplots = np.alltrue([hasattr(ax, 'is_last_row') for ax in self.axes]) if len(self.axes)==1: for label in self.axes[0].get_xticklabels(): label.set_ha(ha) label.set_rotation(rotation) else: if allsubplots: for ax in self.get_axes(): if ax.is_last_row(): for label in ax.get_xticklabels(): label.set_ha(ha) label.set_rotation(rotation) else: for label in ax.get_xticklabels(): label.set_visible(False) ax.set_xlabel('') if allsubplots: self.subplots_adjust(bottom=bottom) def get_children(self): 'get a list of artists contained in the figure' children = [self.patch] children.extend(self.artists) children.extend(self.axes) children.extend(self.lines) children.extend(self.patches) children.extend(self.texts) children.extend(self.images) children.extend(self.legends) return children def contains(self, mouseevent): """ Test whether the mouse event occurred on the figure. Returns True,{} """ if callable(self._contains): return self._contains(self,mouseevent) #inside = mouseevent.x >= 0 and mouseevent.y >= 0 inside = self.bbox.contains(mouseevent.x,mouseevent.y) return inside,{} def get_window_extent(self, *args, **kwargs): 'get the figure bounding box in display space; kwargs are void' return self.bbox def suptitle(self, t, **kwargs): """ Add a centered title to the figure. kwargs are :class:`matplotlib.text.Text` properties. Using figure coordinates, the defaults are: *x* : 0.5 The x location of the text in figure coords *y* : 0.98 The y location of the text in figure coords *horizontalalignment* : 'center' The horizontal alignment of the text *verticalalignment* : 'top' The vertical alignment of the text A :class:`matplotlib.text.Text` instance is returned. Example:: fig.suptitle('this is the figure title', fontsize=12) """ x = kwargs.pop('x', 0.5) y = kwargs.pop('y', 0.98) if ('horizontalalignment' not in kwargs) and ('ha' not in kwargs): kwargs['horizontalalignment'] = 'center' if ('verticalalignment' not in kwargs) and ('va' not in kwargs): kwargs['verticalalignment'] = 'top' t = self.text(x, y, t, **kwargs) return t def set_canvas(self, canvas): """ Set the canvas the contains the figure ACCEPTS: a FigureCanvas instance """ self.canvas = canvas def hold(self, b=None): """ Set the hold state. If hold is None (default), toggle the hold state. Else set the hold state to boolean value b. Eg:: hold() # toggle hold hold(True) # hold is on hold(False) # hold is off """ if b is None: self._hold = not self._hold else: self._hold = b def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, vmin=None, vmax=None, origin=None, **kwargs): """ Adds a non-resampled image to the figure. call signatures:: figimage(X, **kwargs) adds a non-resampled array *X* to the figure. :: figimage(X, xo, yo) with pixel offsets *xo*, *yo*, *X* must be a float array: * If *X* is MxN, assume luminance (grayscale) * If *X* is MxNx3, assume RGB * If *X* is MxNx4, assume RGBA Optional keyword arguments: ========= ========================================================== Keyword Description ========= ========================================================== xo or yo An integer, the *x* and *y* image offset in pixels cmap a :class:`matplotlib.colors.Colormap` instance, eg cm.jet. If *None*, default to the rc ``image.cmap`` value norm a :class:`matplotlib.colors.Normalize` instance. The default is normalization(). This scales luminance -> 0-1 vmin|vmax are used to scale a luminance image to 0-1. If either is *None*, the min and max of the luminance values will be used. Note if you pass a norm instance, the settings for *vmin* and *vmax* will be ignored. alpha the alpha blending value, default is *None* origin [ 'upper' | 'lower' ] Indicates where the [0,0] index of the array is in the upper left or lower left corner of the axes. Defaults to the rc image.origin value ========= ========================================================== figimage complements the axes image (:meth:`~matplotlib.axes.Axes.imshow`) which will be resampled to fit the current axes. If you want a resampled image to fill the entire figure, you can define an :class:`~matplotlib.axes.Axes` with size [0,1,0,1]. An :class:`matplotlib.image.FigureImage` instance is returned. .. plot:: mpl_examples/pylab_examples/figimage_demo.py Additional kwargs are Artist kwargs passed on to :class:`~matplotlib.image.FigureImage` """ if not self._hold: self.clf() im = FigureImage(self, cmap, norm, xo, yo, origin, **kwargs) im.set_array(X) im.set_alpha(alpha) if norm is None: im.set_clim(vmin, vmax) self.images.append(im) return im def set_size_inches(self, *args, **kwargs): """ set_size_inches(w,h, forward=False) Set the figure size in inches Usage:: fig.set_size_inches(w,h) # OR fig.set_size_inches((w,h) ) optional kwarg *forward=True* will cause the canvas size to be automatically updated; eg you can resize the figure window from the shell ACCEPTS: a w,h tuple with w,h in inches """ forward = kwargs.get('forward', False) if len(args)==1: w,h = args[0] else: w,h = args dpival = self.dpi self.bbox_inches.p1 = w, h if forward: dpival = self.dpi canvasw = w*dpival canvash = h*dpival manager = getattr(self.canvas, 'manager', None) if manager is not None: manager.resize(int(canvasw), int(canvash)) def get_size_inches(self): return self.bbox_inches.p1 def get_edgecolor(self): 'Get the edge color of the Figure rectangle' return self.patch.get_edgecolor() def get_facecolor(self): 'Get the face color of the Figure rectangle' return self.patch.get_facecolor() def get_figwidth(self): 'Return the figwidth as a float' return self.bbox_inches.width def get_figheight(self): 'Return the figheight as a float' return self.bbox_inches.height def get_dpi(self): 'Return the dpi as a float' return self.dpi def get_frameon(self): 'get the boolean indicating frameon' return self.frameon def set_edgecolor(self, color): """ Set the edge color of the Figure rectangle ACCEPTS: any matplotlib color - see help(colors) """ self.patch.set_edgecolor(color) def set_facecolor(self, color): """ Set the face color of the Figure rectangle ACCEPTS: any matplotlib color - see help(colors) """ self.patch.set_facecolor(color) def set_dpi(self, val): """ Set the dots-per-inch of the figure ACCEPTS: float """ self.dpi = val def set_figwidth(self, val): """ Set the width of the figure in inches ACCEPTS: float """ self.bbox_inches.x1 = val def set_figheight(self, val): """ Set the height of the figure in inches ACCEPTS: float """ self.bbox_inches.y1 = val def set_frameon(self, b): """ Set whether the figure frame (background) is displayed or invisible ACCEPTS: boolean """ self.frameon = b def delaxes(self, a): 'remove a from the figure and update the current axes' self._axstack.remove(a) for func in self._axobservers: func(self) def _make_key(self, *args, **kwargs): 'make a hashable key out of args and kwargs' def fixitems(items): #items may have arrays and lists in them, so convert them # to tuples for the key ret = [] for k, v in items: if iterable(v): v = tuple(v) ret.append((k,v)) return tuple(ret) def fixlist(args): ret = [] for a in args: if iterable(a): a = tuple(a) ret.append(a) return tuple(ret) key = fixlist(args), fixitems(kwargs.iteritems()) return key @docstring.dedent_interpd def add_axes(self, *args, **kwargs): """ Add an axes at position *rect* [*left*, *bottom*, *width*, *height*] where all quantities are in fractions of figure width and height. kwargs are legal :class:`~matplotlib.axes.Axes` kwargs plus *projection* which sets the projection type of the axes. (For backward compatibility, ``polar=True`` may also be provided, which is equivalent to ``projection='polar'``). Valid values for *projection* are: %(projection_names)s. Some of these projections support additional kwargs, which may be provided to :meth:`add_axes`. Typical usage:: rect = l,b,w,h fig.add_axes(rect) fig.add_axes(rect, frameon=False, axisbg='g') fig.add_axes(rect, polar=True) fig.add_axes(rect, projection='polar') fig.add_axes(ax) If the figure already has an axes with the same parameters, then it will simply make that axes current and return it. If you do not want this behavior, e.g. you want to force the creation of a new Axes, you must use a unique set of args and kwargs. The axes :attr:`~matplotlib.axes.Axes.label` attribute has been exposed for this purpose. Eg., if you want two axes that are otherwise identical to be added to the figure, make sure you give them unique labels:: fig.add_axes(rect, label='axes1') fig.add_axes(rect, label='axes2') In rare circumstances, add_axes may be called with a single argument, an Axes instance already created in the present figure but not in the figure's list of axes. For example, if an axes has been removed with :meth:`delaxes`, it can be restored with:: fig.add_axes(ax) In all cases, the :class:`~matplotlib.axes.Axes` instance will be returned. In addition to *projection*, the following kwargs are supported: %(Axes)s """ if not len(args): return # shortcut the projection "key" modifications later on, if an axes # with the exact args/kwargs exists, return it immediately. key = self._make_key(*args, **kwargs) ax = self._axstack.get(key) if ax is not None: self.sca(ax) return ax if isinstance(args[0], Axes): a = args[0] assert(a.get_figure() is self) else: rect = args[0] projection_class, kwargs, key = \ process_projection_requirements(self, *args, **kwargs) # check that an axes of this type doesn't already exist, if it # does, set it as active and return it ax = self._axstack.get(key) if ax is not None and isinstance(ax, projection_class): self.sca(ax) return ax # create the new axes using the axes class given a = projection_class(self, rect, **kwargs) self._axstack.add(key, a) self.sca(a) return a @docstring.dedent_interpd def add_subplot(self, *args, **kwargs): """ Add a subplot. Examples:: fig.add_subplot(111) # equivalent but more general fig.add_subplot(1,1,1) # add subplot with red background fig.add_subplot(212, axisbg='r') # add a polar subplot fig.add_subplot(111, projection='polar') # add Subplot instance sub fig.add_subplot(sub) *kwargs* are legal :class:`~matplotlib.axes.Axes` kwargs plus *projection*, which chooses a projection type for the axes. (For backward compatibility, *polar=True* may also be provided, which is equivalent to *projection='polar'*). Valid values for *projection* are: %(projection_names)s. Some of these projections support additional *kwargs*, which may be provided to :meth:`add_axes`. The :class:`~matplotlib.axes.Axes` instance will be returned. If the figure already has a subplot with key (*args*, *kwargs*) then it will simply make that subplot current and return it. The following kwargs are supported: %(Axes)s """ if not len(args): return if len(args) == 1 and isinstance(args[0], int): args = tuple([int(c) for c in str(args[0])]) if isinstance(args[0], SubplotBase): a = args[0] assert(a.get_figure() is self) # make a key for the subplot (which includes the axes object id # in the hash) key = self._make_key(*args, **kwargs) else: projection_class, kwargs, key = \ process_projection_requirements(self, *args, **kwargs) # try to find the axes with this key in the stack ax = self._axstack.get(key) if ax is not None: if isinstance(ax, projection_class): # the axes already existed, so set it as active & return self.sca(ax) return ax else: # Undocumented convenience behavior: # subplot(111); subplot(111, projection='polar') # will replace the first with the second. # Without this, add_subplot would be simpler and # more similar to add_axes. self._axstack.remove(ax) a = subplot_class_factory(projection_class)(self, *args, **kwargs) self._axstack.add(key, a) self.sca(a) return a def clf(self, keep_observers=False): """ Clear the figure. Set *keep_observers* to True if, for example, a gui widget is tracking the axes in the figure. """ self.suppressComposite = None self.callbacks = cbook.CallbackRegistry() for ax in tuple(self.axes): # Iterate over the copy. ax.cla() self.delaxes(ax) # removes ax from self._axstack toolbar = getattr(self.canvas, 'toolbar', None) if toolbar is not None: toolbar.update() self._axstack.clear() self.artists = [] self.lines = [] self.patches = [] self.texts=[] self.images = [] self.legends = [] if not keep_observers: self._axobservers = [] def clear(self): """ Clear the figure -- synonym for :meth:`clf`. """ self.clf() @allow_rasterization def draw(self, renderer): """ Render the figure using :class:`matplotlib.backend_bases.RendererBase` instance *renderer*. """ # draw the figure bounding box, perhaps none for white figure if not self.get_visible(): return renderer.open_group('figure') if self.get_tight_layout() and self.axes: try: self.tight_layout(renderer) except ValueError: pass # ValueError can occur when resizing a window. if self.frameon: self.patch.draw(renderer) # a list of (zorder, func_to_call, list_of_args) dsu = [] for a in self.patches: dsu.append( (a.get_zorder(), a, a.draw, [renderer])) for a in self.lines: dsu.append( (a.get_zorder(), a, a.draw, [renderer])) for a in self.artists: dsu.append( (a.get_zorder(), a, a.draw, [renderer])) # override the renderer default if self.suppressComposite # is not None not_composite = renderer.option_image_nocomposite() if self.suppressComposite is not None: not_composite = self.suppressComposite if len(self.images)<=1 or not_composite or \ not cbook.allequal([im.origin for im in self.images]): for a in self.images: dsu.append( (a.get_zorder(), a, a.draw, [renderer])) else: # make a composite image blending alpha # list of (_image.Image, ox, oy) mag = renderer.get_image_magnification() ims = [(im.make_image(mag), im.ox, im.oy) for im in self.images] im = _image.from_images(self.bbox.height * mag, self.bbox.width * mag, ims) im.is_grayscale = False l, b, w, h = self.bbox.bounds def draw_composite(): gc = renderer.new_gc() gc.set_clip_rectangle(self.bbox) gc.set_clip_path(self.get_clip_path()) renderer.draw_image(gc, l, b, im) gc.restore() dsu.append((self.images[0].get_zorder(), self.images[0], draw_composite, [])) # render the axes for a in self.axes: dsu.append( (a.get_zorder(), a, a.draw, [renderer])) # render the figure text for a in self.texts: dsu.append( (a.get_zorder(), a, a.draw, [renderer])) for a in self.legends: dsu.append( (a.get_zorder(), a, a.draw, [renderer])) dsu = [row for row in dsu if not row[1].get_animated()] dsu.sort(key=itemgetter(0)) for zorder, a, func, args in dsu: func(*args) renderer.close_group('figure') self._cachedRenderer = renderer self.canvas.draw_event(renderer) def draw_artist(self, a): """ draw :class:`matplotlib.artist.Artist` instance *a* only -- this is available only after the figure is drawn """ assert self._cachedRenderer is not None a.draw(self._cachedRenderer) def get_axes(self): return self.axes def legend(self, handles, labels, *args, **kwargs): """ Place a legend in the figure. Labels are a sequence of strings, handles is a sequence of :class:`~matplotlib.lines.Line2D` or :class:`~matplotlib.patches.Patch` instances, and loc can be a string or an integer specifying the legend location USAGE:: legend( (line1, line2, line3), ('label1', 'label2', 'label3'), 'upper right') The *loc* location codes are:: 'best' : 0, (currently not supported for figure legends) 'upper right' : 1, 'upper left' : 2, 'lower left' : 3, 'lower right' : 4, 'right' : 5, 'center left' : 6, 'center right' : 7, 'lower center' : 8, 'upper center' : 9, 'center' : 10, *loc* can also be an (x,y) tuple in figure coords, which specifies the lower left of the legend box. figure coords are (0,0) is the left, bottom of the figure and 1,1 is the right, top. Keyword arguments: *prop*: [ *None* | FontProperties | dict ] A :class:`matplotlib.font_manager.FontProperties` instance. If *prop* is a dictionary, a new instance will be created with *prop*. If *None*, use rc settings. *numpoints*: integer The number of points in the legend line, default is 4 *scatterpoints*: integer The number of points in the legend line, default is 4 *scatteroffsets*: list of floats a list of yoffsets for scatter symbols in legend *markerscale*: [ *None* | scalar ] The relative size of legend markers vs. original. If *None*, use rc settings. *fancybox*: [ *None* | *False* | *True* ] if *True*, draw a frame with a round fancybox. If *None*, use rc *shadow*: [ *None* | *False* | *True* ] If *True*, draw a shadow behind legend. If *None*, use rc settings. *ncol* : integer number of columns. default is 1 *mode* : [ "expand" | *None* ] if mode is "expand", the legend will be horizontally expanded to fill the axes area (or *bbox_to_anchor*) *title* : string the legend title Padding and spacing between various elements use following keywords parameters. The dimensions of these values are given as a fraction of the fontsize. Values from rcParams will be used if None. ================ ================================================================== Keyword Description ================ ================================================================== borderpad the fractional whitespace inside the legend border labelspacing the vertical space between the legend entries handlelength the length of the legend handles handletextpad the pad between the legend handle and text borderaxespad the pad between the axes and legend border columnspacing the spacing between columns ================ ================================================================== .. Note:: Not all kinds of artist are supported by the legend. See LINK (FIXME) for details. **Example:** .. plot:: mpl_examples/pylab_examples/figlegend_demo.py """ l = Legend(self, handles, labels, *args, **kwargs) self.legends.append(l) return l @docstring.dedent_interpd def text(self, x, y, s, *args, **kwargs): """ Add text to figure. Call signature:: text(x, y, s, fontdict=None, **kwargs) Add text to figure at location *x*, *y* (relative 0-1 coords). See :func:`~matplotlib.pyplot.text` for the meaning of the other arguments. kwargs control the :class:`~matplotlib.text.Text` properties: %(Text)s """ override = _process_text_args({}, *args, **kwargs) t = Text( x=x, y=y, text=s, ) t.update(override) self._set_artist_props(t) self.texts.append(t) return t def _set_artist_props(self, a): if a!= self: a.set_figure(self) a.set_transform(self.transFigure) @docstring.dedent_interpd def gca(self, **kwargs): """ Return the current axes, creating one if necessary The following kwargs are supported for ensuring the returned axes adheres to the given projection etc., and for axes creation if the active axes does not exist: %(Axes)s """ ckey, cax = self._axstack.current_key_axes() # if there exists an axes on the stack see if it maches # the desired axes configuration if cax is not None: # if no kwargs are given just return the current axes # this is a convenience for gca() on axes such as polar etc. if not kwargs: return cax # if the user has specified particular projection detail # then build up a key which can represent this else: # we don't want to modify the original kwargs # so take a copy so that we can do what we like to it kwargs_copy = kwargs.copy() projection_class, _, key = \ process_projection_requirements(self, **kwargs_copy) # let the returned axes have any gridspec by removing it from the key ckey = ckey[1:] key = key[1:] # if the cax matches this key then return the axes, otherwise # continue and a new axes will be created if key == ckey and isinstance(cax, projection_class): return cax # no axes found, so create one which spans the figure return self.add_subplot(1, 1, 1, **kwargs) def sca(self, a): 'Set the current axes to be a and return a' self._axstack.bubble(a) for func in self._axobservers: func(self) return a def _gci(self): """ helper for :func:`~matplotlib.pyplot.gci`; do not use elsewhere. """ for ax in reversed(self.axes): im = ax._gci() if im is not None: return im return None def add_axobserver(self, func): 'whenever the axes state change, ``func(self)`` will be called' self._axobservers.append(func) def savefig(self, *args, **kwargs): """ Save the current figure. Call signature:: savefig(fname, dpi=None, facecolor='w', edgecolor='w', orientation='portrait', papertype=None, format=None, transparent=False, bbox_inches=None, pad_inches=0.1) The output formats available depend on the backend being used. Arguments: *fname*: A string containing a path to a filename, or a Python file-like object, or possibly some backend-dependent object such as :class:`~matplotlib.backends.backend_pdf.PdfPages`. If *format* is *None* and *fname* is a string, the output format is deduced from the extension of the filename. If the filename has no extension, the value of the rc parameter ``savefig.format`` is used. If *fname* is not a string, remember to specify *format* to ensure that the correct backend is used. Keyword arguments: *dpi*: [ *None* | ``scalar > 0`` ] The resolution in dots per inch. If *None* it will default to the value ``savefig.dpi`` in the matplotlibrc file. *facecolor*, *edgecolor*: the colors of the figure rectangle *orientation*: [ 'landscape' | 'portrait' ] not supported on all backends; currently only on postscript output *papertype*: One of 'letter', 'legal', 'executive', 'ledger', 'a0' through 'a10', 'b0' through 'b10'. Only supported for postscript output. *format*: One of the file extensions supported by the active backend. Most backends support png, pdf, ps, eps and svg. *transparent*: If *True*, the axes patches will all be transparent; the figure patch will also be transparent unless facecolor and/or edgecolor are specified via kwargs. This is useful, for example, for displaying a plot on top of a colored background on a web page. The transparency of these patches will be restored to their original values upon exit of this function. *bbox_inches*: Bbox in inches. Only the given portion of the figure is saved. If 'tight', try to figure out the tight bbox of the figure. *pad_inches*: Amount of padding around the figure when bbox_inches is 'tight'. *bbox_extra_artists*: A list of extra artists that will be considered when the tight bbox is calculated. """ kwargs.setdefault('dpi', rcParams['savefig.dpi']) transparent = kwargs.pop('transparent', False) if transparent: kwargs.setdefault('facecolor', 'none') kwargs.setdefault('edgecolor', 'none') original_axes_colors = [] for ax in self.axes: patch = ax.patch original_axes_colors.append((patch.get_facecolor(), patch.get_edgecolor())) patch.set_facecolor('none') patch.set_edgecolor('none') else: kwargs.setdefault('facecolor', rcParams['savefig.facecolor']) kwargs.setdefault('edgecolor', rcParams['savefig.edgecolor']) self.canvas.print_figure(*args, **kwargs) if transparent: for ax, cc in zip(self.axes, original_axes_colors): ax.patch.set_facecolor(cc[0]) ax.patch.set_edgecolor(cc[1]) @docstring.dedent_interpd def colorbar(self, mappable, cax=None, ax=None, **kw): """ Create a colorbar for a ScalarMappable instance, *mappable*. Documentation for the pylab thin wrapper: %(colorbar_doc)s """ if ax is None: ax = self.gca() use_gridspec = kw.pop("use_gridspec", True) if cax is None: if use_gridspec and isinstance(ax, SubplotBase): cax, kw = cbar.make_axes_gridspec(ax, **kw) else: cax, kw = cbar.make_axes(ax, **kw) cax.hold(True) cb = cbar.colorbar_factory(cax, mappable, **kw) self.sca(ax) return cb def subplots_adjust(self, *args, **kwargs): """ Call signature:: subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None) Update the :class:`SubplotParams` with *kwargs* (defaulting to rc when *None*) and update the subplot locations """ self.subplotpars.update(*args, **kwargs) import matplotlib.axes for ax in self.axes: if not isinstance(ax, matplotlib.axes.SubplotBase): # Check if sharing a subplots axis if ax._sharex is not None and isinstance(ax._sharex, matplotlib.axes.SubplotBase): ax._sharex.update_params() ax.set_position(ax._sharex.figbox) elif ax._sharey is not None and isinstance(ax._sharey, matplotlib.axes.SubplotBase): ax._sharey.update_params() ax.set_position(ax._sharey.figbox) else: ax.update_params() ax.set_position(ax.figbox) def ginput(self, n=1, timeout=30, show_clicks=True, mouse_add=1, mouse_pop=3, mouse_stop=2): """ Call signature:: ginput(self, n=1, timeout=30, show_clicks=True, mouse_add=1, mouse_pop=3, mouse_stop=2) Blocking call to interact with the figure. This will wait for *n* clicks from the user and return a list of the coordinates of each click. If *timeout* is zero or negative, does not timeout. If *n* is zero or negative, accumulate clicks until a middle click (or potentially both mouse buttons at once) terminates the input. Right clicking cancels last input. The buttons used for the various actions (adding points, removing points, terminating the inputs) can be overriden via the arguments *mouse_add*, *mouse_pop* and *mouse_stop*, that give the associated mouse button: 1 for left, 2 for middle, 3 for right. The keyboard can also be used to select points in case your mouse does not have one or more of the buttons. The delete and backspace keys act like right clicking (i.e., remove last point), the enter key terminates input and any other key (not already used by the window manager) selects a point. """ blocking_mouse_input = BlockingMouseInput(self, mouse_add =mouse_add, mouse_pop =mouse_pop, mouse_stop=mouse_stop) return blocking_mouse_input(n=n, timeout=timeout, show_clicks=show_clicks) def waitforbuttonpress(self, timeout=-1): """ Call signature:: waitforbuttonpress(self, timeout=-1) Blocking call to interact with the figure. This will return True is a key was pressed, False if a mouse button was pressed and None if *timeout* was reached without either being pressed. If *timeout* is negative, does not timeout. """ blocking_input = BlockingKeyMouseInput(self) return blocking_input(timeout=timeout) def get_default_bbox_extra_artists(self): bbox_extra_artists = [t for t in self.texts if t.get_visible()] for ax in self.axes: if ax.get_visible(): bbox_extra_artists.extend(ax.get_default_bbox_extra_artists()) return bbox_extra_artists def get_tightbbox(self, renderer): """ Return a (tight) bounding box of the figure in inches. It only accounts axes title, axis labels, and axis ticklabels. Needs improvement. """ bb = [] for ax in self.axes: if ax.get_visible(): bb.append(ax.get_tightbbox(renderer)) _bbox = Bbox.union([b for b in bb if b.width!=0 or b.height!=0]) bbox_inches = TransformedBbox(_bbox, Affine2D().scale(1./self.dpi)) return bbox_inches def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, rect=None): """ Adjust subplot parameters to give specified padding. Parameters: *pad* : float padding between the figure edge and the edges of subplots, as a fraction of the font-size. *h_pad*, *w_pad* : float padding (height/width) between edges of adjacent subplots. Defaults to `pad_inches`. *rect* : if rect is given, it is interpreted as a rectangle (left, bottom, right, top) in the normalized figure coordinate that the whole subplots area (including labels) will fit into. Default is (0, 0, 1, 1). """ from tight_layout import get_renderer, get_tight_layout_figure no_go = [ax for ax in self.axes if not isinstance(ax, SubplotBase)] if no_go: warnings.Warn("Cannot use tight_layout;" " all Axes must descend from SubplotBase") return if renderer is None: renderer = get_renderer(self) kwargs = get_tight_layout_figure(self, self.axes, renderer, pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) self.subplots_adjust(**kwargs)