def outside_legend( fig: plt.Figure, ax: plt.Axes, legend_padding: float = 0.04, legend_height: float = 0.3, **kwargs, ) -> None: """Plots a legend immediately above the figure axes. Args: fig: The figure to plot the legend on. ax: The axes to plot the legend above. legend_padding: Padding between top of axes and bottom of legend, in inches. legend_height: Height of legend, in inches. **kwargs: Passed through to `fig.legend`. """ _width, height = fig.get_size_inches() pos = ax.get_position() legend_left = pos.x0 legend_right = pos.x0 + pos.width legend_width = legend_right - legend_left legend_bottom = pos.y0 + pos.height + legend_padding / height legend_height = legend_height / height bbox = (legend_left, legend_bottom, legend_width, legend_height) fig.legend( loc="lower left", bbox_to_anchor=bbox, bbox_transform=fig.transFigure, mode="expand", **kwargs, )
def _mpl_figure_to_rgb_img(fig: plt.Figure, height, width): fig.set_dpi(100) fig.set_size_inches(width / 100, height / 100) canvas = fig.canvas canvas.draw() width, height = np.round(fig.get_size_inches() * fig.get_dpi()).astype(int) # image = np.fromstring(fig.canvas.tostring_rgb(), dtype='uint8') img = np.fromstring(canvas.tostring_rgb(), dtype='uint8').reshape(height, width, 3) plt.close(fig) return img
def _( figure: Figure, scale: str = "down", inherit_font: bool = True, tight_layout: bool = True, bbox_inches: str = None, ) -> SVGContentConfiguration: if tight_layout: figure.tight_layout() fig_size = figure.get_size_inches() with io.StringIO() as str_io: figure.savefig(str_io, format="svg", transparent=True, bbox_inches=bbox_inches) svg = str_io.getvalue() return SVGContentConfiguration( data=clean_svg( svg, scale=scale, width=fig_size[0], height=fig_size[1], inherit_font=inherit_font, ) )
class MplView(FigureCanvas, QWidget): """ Base class for matplotlib based views. This handles graph canvas setup, toolbar initialisation and figure save options. """ is_floatable_view = True is_mpl_toolbar_enabled = True """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.).""" def __init__(self, parent, width=5, height=5, dpi=96, **kwargs): self.v = parent self.fig = Figure(figsize=(width, height), dpi=dpi) self.ax = self.fig.add_subplot(111) FigureCanvas.__init__(self, self.fig) self.setParent(parent) FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding, QSizePolicy.Expanding) FigureCanvas.updateGeometry(self) # Install navigation handler; we need to provide a Qt interface that can handle multiple # plots in a window under separate tabs # self.navigation = MplNavigationHandler(self) self._current_axis_bounds = None def generate(self): pass def saveAsImage(self, settings): # Size, dots per metre (for print), resample (redraw) image filename, _ = QFileDialog.getSaveFileName(self, 'Save current figure', '', "Tagged Image File Format (*.tif);;\ Portable Document File (*.pdf);;\ Encapsulated Postscript File (*.eps);;\ Scalable Vector Graphics (*.svg);;\ Portable Network Graphics (*.png)") if filename: size = settings.get_print_size('in') dpi = settings.get_dots_per_inch() prev_size = self.fig.get_size_inches() self.fig.set_size_inches(*size) self.fig.savefig(filename, dpi=dpi) self.fig.set_size_inches(*prev_size) self.redraw() def get_size_inches(self, dpi): s = self.size() return s.width()/dpi, s.height()/dpi def redraw(self): #FIXME: Ugly hack to refresh the canvas self.resize( self.size() - QSize(1,1) ) self.resize( self.size() + QSize(1,1) ) def resizeEvent(self,e): FigureCanvas.resizeEvent(self,e) def get_text_bbox_screen_coords(self, t): bbox = t.get_window_extent(self.get_renderer()) return bbox.get_points() def get_text_bbox_data_coords(self, t): bbox = t.get_window_extent(self.get_renderer()) axbox = bbox.transformed(self.ax.transData.inverted()) return axbox.get_points() def extend_limits(self, a, b): # Extend a to meet b where applicable ax, ay = list(a[0]), list(a[1]) bx, by = b[:, 0], b[:, 1] ax[0] = bx[0] if bx[0] < ax[0] else ax[0] ax[1] = bx[1] if bx[1] > ax[1] else ax[1] ay[0] = by[0] if by[0] < ay[0] else ay[0] ay[1] = by[1] if by[1] > ay[1] else ay[1] return [ax,ay] 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))