From 608a6e6c14a8135f82454959ad9f2e09b32156d4 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 25 Aug 2020 08:36:59 -0700 Subject: [PATCH] ENH: Subfigures Co-authored-by: Elliott Sales de Andrade --- doc/api/figure_api.rst | 25 +- .../development/18356-JMK.rst | 17 + doc/users/next_whats_new/subfigures.rst | 53 + .../gridspec_nested.py | 4 + .../subplots_axes_and_figures/subfigures.py | 148 + .../subplots_demo.py | 2 +- lib/matplotlib/_constrained_layout.py | 53 +- lib/matplotlib/axes/_axes.py | 2 +- lib/matplotlib/axes/_base.py | 8 +- lib/matplotlib/figure.py | 3252 +++++++++-------- lib/matplotlib/patches.py | 55 +- lib/matplotlib/pyplot.py | 6 +- .../test_figure/test_subfigure.png | Bin 0 -> 62512 bytes .../test_figure/test_subfigure_ss.png | Bin 0 -> 52726 bytes lib/matplotlib/tests/test_figure.py | 47 + lib/matplotlib/tests/test_polar.py | 8 +- lib/matplotlib/text.py | 36 +- lib/mpl_toolkits/axes_grid1/colorbar.py | 4 +- lib/mpl_toolkits/mplot3d/axes3d.py | 7 +- lib/mpl_toolkits/tests/test_mplot3d.py | 2 +- tutorials/advanced/transforms_tutorial.py | 7 + 21 files changed, 2194 insertions(+), 1542 deletions(-) create mode 100644 doc/api/next_api_changes/development/18356-JMK.rst create mode 100644 doc/users/next_whats_new/subfigures.rst create mode 100644 examples/subplots_axes_and_figures/subfigures.py create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/test_subfigure.png create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_ss.png diff --git a/doc/api/figure_api.rst b/doc/api/figure_api.rst index 3c1deb9fdc3c..1beb0a701b26 100644 --- a/doc/api/figure_api.rst +++ b/doc/api/figure_api.rst @@ -5,26 +5,5 @@ .. currentmodule:: matplotlib.figure .. automodule:: matplotlib.figure - :no-members: - :no-inherited-members: - -Classes -------- - -.. autosummary:: - :toctree: _as_gen/ - :template: autosummary.rst - :nosignatures: - - Figure - SubplotParams - -Functions ---------- - -.. autosummary:: - :toctree: _as_gen/ - :template: autosummary.rst - :nosignatures: - - figaspect + :members: + :inherited-members: diff --git a/doc/api/next_api_changes/development/18356-JMK.rst b/doc/api/next_api_changes/development/18356-JMK.rst new file mode 100644 index 000000000000..14d567c1fa38 --- /dev/null +++ b/doc/api/next_api_changes/development/18356-JMK.rst @@ -0,0 +1,17 @@ +FigureBase class added, and Figure class made a child +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The new subfigure feature motivated some re-organization of the +`.figure.Figure` class, so that the new `.figure.SubFigure` class could have +all the capabilities of a figure. + +The `.figure.Figure` class is now a subclass of `.figure.FigureBase`, where +`.figure.FigureBase` contains figure-level artist addition routines, and +the `.figure.Figure` subclass just contains features that are unique to the +outer figure. + +Note that there is a new *transSubfigure* transform +associated with the subfigure. This transform also exists for a +`.Figure` instance, and is equal to *transFigure* in that case, +so code that uses the transform stack that wants to place objects on either +the parent figure or one of the subfigures should use *transSubfigure*. diff --git a/doc/users/next_whats_new/subfigures.rst b/doc/users/next_whats_new/subfigures.rst new file mode 100644 index 000000000000..3290afa460f4 --- /dev/null +++ b/doc/users/next_whats_new/subfigures.rst @@ -0,0 +1,53 @@ +New subfigure functionality +--------------------------- +New `.figure.Figure.add_subfigure` and `.figure.Figure.subfigures` +functionalities allow creating virtual figures within figures. Similar +nesting was previously done with nested gridspecs +( see :doc:`/gallery/subplots_axes_and_figures/gridspec_nested`). However, this +did not allow localized figure artists (i.e. a colorbar or suptitle) that +only pertained to each subgridspec. + +The new methods `.figure.Figure.add_subfigure` and `.figure.Figure.subfigures` +are meant to rhyme with `.figure.Figure.add_subplot` and +`.figure.Figure.subplots` and have most of the same arguments. + +See :doc:`/gallery/subplots_axes_and_figures/subfigures`. + +.. note:: + + The subfigure functionality is experimental API as of v3.4. + +.. plot:: + + def example_plot(ax, fontsize=12, hide_labels=False): + pc = ax.pcolormesh(np.random.randn(30, 30)) + if not hide_labels: + ax.set_xlabel('x-label', fontsize=fontsize) + ax.set_ylabel('y-label', fontsize=fontsize) + ax.set_title('Title', fontsize=fontsize) + return pc + + np.random.seed(19680808) + fig = plt.figure(constrained_layout=True, figsize=(10, 4)) + subfigs = fig.subfigures(1, 2, wspace=0.07) + + axsLeft = subfigs[0].subplots(1, 2, sharey=True) + subfigs[0].set_facecolor('0.75') + for ax in axsLeft: + pc = example_plot(ax) + subfigs[0].suptitle('Left plots', fontsize='x-large') + subfigs[0].colorbar(pc, shrink=0.6, ax=axsLeft, location='bottom') + + axsRight = subfigs[1].subplots(3, 1, sharex=True) + for nn, ax in enumerate(axsRight): + pc = example_plot(ax, hide_labels=True) + if nn == 2: + ax.set_xlabel('xlabel') + if nn == 1: + ax.set_ylabel('ylabel') + subfigs[1].colorbar(pc, shrink=0.6, ax=axsRight) + subfigs[1].suptitle('Right plots', fontsize='x-large') + + fig.suptitle('Figure suptitle', fontsize='xx-large') + + plt.show() diff --git a/examples/subplots_axes_and_figures/gridspec_nested.py b/examples/subplots_axes_and_figures/gridspec_nested.py index 20c211689e68..e47106e5d47c 100644 --- a/examples/subplots_axes_and_figures/gridspec_nested.py +++ b/examples/subplots_axes_and_figures/gridspec_nested.py @@ -6,6 +6,10 @@ GridSpecs can be nested, so that a subplot from a parent GridSpec can set the position for a nested grid of subplots. +Note that the same functionality can be achieved more directly with +`~.figure.FigureBase.subfigures`; see +:doc:`/gallery/subplots_axes_and_figures/subfigures`. + """ import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec diff --git a/examples/subplots_axes_and_figures/subfigures.py b/examples/subplots_axes_and_figures/subfigures.py new file mode 100644 index 000000000000..e0f56618754f --- /dev/null +++ b/examples/subplots_axes_and_figures/subfigures.py @@ -0,0 +1,148 @@ +""" +================= +Figure subfigures +================= + +Sometimes it is desirable to have a figure with two different layouts in it. +This can be achieved with +:doc:`nested gridspecs`, +but having a virtual figure with its own artists is helpful, so +Matplotlib also has "subfigures", accessed by calling +`matplotlib.figure.Figure.add_subfigure` in a way that is analagous to +`matplotlib.figure.Figure.add_subplot`, or +`matplotlib.figure.Figure.subfigures` to make an array of subfigures. Note +that subfigures can also have their own child subfigures. + +.. note:: + ``subfigure`` is new in v3.4, and the API is still provisional. + +""" +import matplotlib.pyplot as plt +import numpy as np + + +def example_plot(ax, fontsize=12, hide_labels=False): + pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2.5, vmax=2.5) + if not hide_labels: + ax.set_xlabel('x-label', fontsize=fontsize) + ax.set_ylabel('y-label', fontsize=fontsize) + ax.set_title('Title', fontsize=fontsize) + return pc + +np.random.seed(19680808) +# gridspec inside gridspec +fig = plt.figure(constrained_layout=True, figsize=(10, 4)) +subfigs = fig.subfigures(1, 2, wspace=0.07) + +axsLeft = subfigs[0].subplots(1, 2, sharey=True) +subfigs[0].set_facecolor('0.75') +for ax in axsLeft: + pc = example_plot(ax) +subfigs[0].suptitle('Left plots', fontsize='x-large') +subfigs[0].colorbar(pc, shrink=0.6, ax=axsLeft, location='bottom') + +axsRight = subfigs[1].subplots(3, 1, sharex=True) +for nn, ax in enumerate(axsRight): + pc = example_plot(ax, hide_labels=True) + if nn == 2: + ax.set_xlabel('xlabel') + if nn == 1: + ax.set_ylabel('ylabel') + +subfigs[1].set_facecolor('0.85') +subfigs[1].colorbar(pc, shrink=0.6, ax=axsRight) +subfigs[1].suptitle('Right plots', fontsize='x-large') + +fig.suptitle('Figure suptitle', fontsize='xx-large') + +plt.show() + +############################################################################## +# It is possible to mix subplots and subfigures using +# `matplotlib.figure.Figure.add_subfigure`. This requires getting +# the gridspec that the subplots are laid out on. + +fig, axs = plt.subplots(2, 3, constrained_layout=True, figsize=(10, 4)) +gridspec = axs[0, 0].get_subplotspec().get_gridspec() + +# clear the left column for the subfigure: +for a in axs[:, 0]: + a.remove() + +# plot data in remaining axes: +for a in axs[:, 1:].flat: + a.plot(np.arange(10)) + +# make the subfigure in the empy gridspec slots: +subfig = fig.add_subfigure(gridspec[:, 0]) + +axsLeft = subfig.subplots(1, 2, sharey=True) +subfig.set_facecolor('0.75') +for ax in axsLeft: + pc = example_plot(ax) +subfig.suptitle('Left plots', fontsize='x-large') +subfig.colorbar(pc, shrink=0.6, ax=axsLeft, location='bottom') + +fig.suptitle('Figure suptitle', fontsize='xx-large') +plt.show() + +############################################################################## +# Subfigures can have different widths and heights. This is exactly the +# same example as the first example, but *width_ratios* has been changed: + +fig = plt.figure(constrained_layout=True, figsize=(10, 4)) +subfigs = fig.subfigures(1, 2, wspace=0.07, width_ratios=[2, 1]) + +axsLeft = subfigs[0].subplots(1, 2, sharey=True) +subfigs[0].set_facecolor('0.75') +for ax in axsLeft: + pc = example_plot(ax) +subfigs[0].suptitle('Left plots', fontsize='x-large') +subfigs[0].colorbar(pc, shrink=0.6, ax=axsLeft, location='bottom') + +axsRight = subfigs[1].subplots(3, 1, sharex=True) +for nn, ax in enumerate(axsRight): + pc = example_plot(ax, hide_labels=True) + if nn == 2: + ax.set_xlabel('xlabel') + if nn == 1: + ax.set_ylabel('ylabel') + +subfigs[1].set_facecolor('0.85') +subfigs[1].colorbar(pc, shrink=0.6, ax=axsRight) +subfigs[1].suptitle('Right plots', fontsize='x-large') + +fig.suptitle('Figure suptitle', fontsize='xx-large') + +plt.show() + +############################################################################## +# Subfigures can be also be nested: + +fig = plt.figure(constrained_layout=True, figsize=(10, 8)) + +fig.suptitle('fig') + +subfigs = fig.subfigures(1, 2, wspace=0.07) + +subfigs[0].set_facecolor('coral') +subfigs[0].suptitle('subfigs[0]') + +subfigs[1].set_facecolor('coral') +subfigs[1].suptitle('subfigs[1]') + +subfigsnest = subfigs[0].subfigures(2, 1, height_ratios=[1, 1.4]) +subfigsnest[0].suptitle('subfigsnest[0]') +subfigsnest[0].set_facecolor('r') +axsnest0 = subfigsnest[0].subplots(1, 2, sharey=True) +for nn, ax in enumerate(axsnest0): + pc = example_plot(ax, hide_labels=True) +subfigsnest[0].colorbar(pc, ax=axsnest0) + +subfigsnest[1].suptitle('subfigsnest[1]') +subfigsnest[1].set_facecolor('g') +axsnest1 = subfigsnest[1].subplots(3, 1, sharex=True) + +axsRight = subfigs[1].subplots(2, 2) + +plt.show() diff --git a/examples/subplots_axes_and_figures/subplots_demo.py b/examples/subplots_axes_and_figures/subplots_demo.py index 522275decf1e..bbc8afa469fa 100644 --- a/examples/subplots_axes_and_figures/subplots_demo.py +++ b/examples/subplots_axes_and_figures/subplots_demo.py @@ -144,7 +144,7 @@ # Still there remains an unused empty space between the subplots. # # To precisely control the positioning of the subplots, one can explicitly -# create a `.GridSpec` with `.add_gridspec`, and then call its +# create a `.GridSpec` with `.Figure.add_gridspec`, and then call its # `~.GridSpecBase.subplots` method. For example, we can reduce the height # between vertical subplots using ``add_gridspec(hspace=0)``. # diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 60fc4e0d5c8b..181b8d70387c 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -103,11 +103,10 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, # larger/smaller). This second reposition tends to be much milder, # so doing twice makes things work OK. - # make margins for all the axes and subpanels in the + # make margins for all the axes and subfigures in the # figure. Add margins for colorbars... _make_layout_margins(fig, renderer, h_pad=h_pad, w_pad=w_pad, hspace=hspace, wspace=wspace) - _make_margin_suptitles(fig, renderer, h_pad=h_pad, w_pad=w_pad) # if a layout is such that a columns (or rows) margin has no @@ -132,7 +131,7 @@ def _check_no_collapsed_axes(fig): """ Check that no axes have collapsed to zero size. """ - for panel in fig.panels: + for panel in fig.subfigs: ok = _check_no_collapsed_axes(panel) if not ok: return False @@ -198,7 +197,7 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, Then make room for colorbars. """ - for panel in fig.panels: # recursively make child panel margins + for panel in fig.subfigs: # recursively make child panel margins ss = panel._subplotspec _make_layout_margins(panel, renderer, w_pad=w_pad, h_pad=h_pad, hspace=hspace, wspace=wspace) @@ -207,8 +206,7 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, hspace=hspace, wspace=wspace) panel._layoutgrid.parent.edit_outer_margin_mins(margins, ss) - # for ax in [a for a in fig._localaxes if hasattr(a, 'get_subplotspec')]: - for ax in fig.get_axes(): + for ax in fig._localaxes.as_list(): if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout(): continue @@ -223,7 +221,6 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, hspace=hspace, wspace=wspace) margin0 = margin.copy() pos, bbox = _get_pos_and_bbox(ax, renderer) - # the margin is the distance between the bounding box of the axes # and its position (plus the padding from above) margin['left'] += pos.x0 - bbox.x0 @@ -279,13 +276,17 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, def _make_margin_suptitles(fig, renderer, *, w_pad=0, h_pad=0): # Figure out how large the suptitle is and make the # top level figure margin larger. - for panel in fig.panels: + for panel in fig.subfigs: _make_margin_suptitles(panel, renderer, w_pad=w_pad, h_pad=h_pad) - invTransFig = fig.transPanel.inverted().transform_bbox - - w_pad, h_pad = (fig.transPanel - fig.transFigure).transform((w_pad, h_pad)) if fig._suptitle is not None and fig._suptitle.get_in_layout(): + invTransFig = fig.transSubfigure.inverted().transform_bbox + parenttrans = fig.transFigure + w_pad, h_pad = (fig.transSubfigure - + parenttrans).transform((w_pad, 1 - h_pad)) + w_pad, one = (fig.transSubfigure - + parenttrans).transform((w_pad, 1)) + h_pad = one - h_pad bbox = invTransFig(fig._suptitle.get_tightbbox(renderer)) p = fig._suptitle.get_position() fig._suptitle.set_position((p[0], 1-h_pad)) @@ -317,7 +318,7 @@ def _match_submerged_margins(fig): See test_constrained_layout::test_constrained_layout12 for an example. """ - for panel in fig.panels: + for panel in fig.subfigs: _match_submerged_margins(panel) axs = [a for a in fig.get_axes() if (hasattr(a, 'get_subplotspec') @@ -429,7 +430,7 @@ def _get_pos_and_bbox(ax, renderer): fig = ax.figure pos = ax.get_position(original=True) # pos is in panel co-ords, but we need in figure for the layout - pos = pos.transformed(fig.transPanel - fig.transFigure) + pos = pos.transformed(fig.transSubfigure - fig.transFigure) try: tightbbox = ax.get_tightbbox(renderer=renderer, for_layout_only=True) except TypeError: @@ -446,18 +447,16 @@ def _reposition_axes(fig, renderer, *, w_pad=0, h_pad=0, hspace=0, wspace=0): """ Reposition all the axes based on the new inner bounding box. """ - trans_fig_to_panel = fig.transFigure - fig.transPanel - for panel in fig.panels: - bbox = panel._layoutgrid.get_outer_bbox() - panel._redo_transform_rel_fig( - bbox=bbox.transformed(trans_fig_to_panel)) - _reposition_axes(panel, renderer, + trans_fig_to_subfig = fig.transFigure - fig.transSubfigure + for sfig in fig.subfigs: + bbox = sfig._layoutgrid.get_outer_bbox() + sfig._redo_transform_rel_fig( + bbox=bbox.transformed(trans_fig_to_subfig)) + _reposition_axes(sfig, renderer, w_pad=w_pad, h_pad=h_pad, wspace=wspace, hspace=hspace) - # for ax in fig._localaxes: - # if not hasattr(a, 'get_subplotspec'): - for ax in fig.get_axes(): + for ax in fig._localaxes.as_list(): if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout(): continue @@ -475,7 +474,7 @@ def _reposition_axes(fig, renderer, *, w_pad=0, h_pad=0, hspace=0, wspace=0): cols=ss.colspan) # transform from figure to panel for set_position: - newbbox = trans_fig_to_panel.transform_bbox(bbox) + newbbox = trans_fig_to_subfig.transform_bbox(bbox) ax._set_position(newbbox) # move the colorbars: @@ -512,7 +511,7 @@ def _reposition_colorbar(cbax, renderer, *, offset=None): gs = parents[0].get_gridspec() ncols, nrows = gs.ncols, gs.nrows fig = cbax.figure - trans_fig_to_panel = fig.transFigure - fig.transPanel + trans_fig_to_subfig = fig.transFigure - fig.transSubfigure cb_rspans, cb_cspans = _get_cb_parent_spans(cbax) bboxparent = gs._layoutgrid.get_bbox_for_cb(rows=cb_rspans, cols=cb_cspans) @@ -562,8 +561,8 @@ def _reposition_colorbar(cbax, renderer, *, offset=None): offset['bottom'] += cbbbox.height + cbpad pbcb = pbcb.translated(0, dy) - pbcb = trans_fig_to_panel.transform_bbox(pbcb) - cbax.set_transform(fig.transPanel) + pbcb = trans_fig_to_subfig.transform_bbox(pbcb) + cbax.set_transform(fig.transSubfigure) cbax._set_position(pbcb) cbax.set_aspect(aspect, anchor=anchor, adjustable='box') return offset @@ -576,7 +575,7 @@ def _reset_margins(fig): Margins are usually set as a minimum, so if the figure gets smaller the minimum needs to be zero in order for it to grow again. """ - for span in fig.panels: + for span in fig.subfigs: _reset_margins(span) for ax in fig.axes: if hasattr(ax, 'get_subplotspec') and ax.get_in_layout(): diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 58348483cac9..42e071e4c477 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -449,7 +449,7 @@ def indicate_inset(self, bounds, inset_ax=None, *, transform=None, # decide which two of the lines to keep visible.... pos = inset_ax.get_position() - bboxins = pos.transformed(self.figure.transFigure) + bboxins = pos.transformed(self.figure.transSubfigure) rectbbox = mtransforms.Bbox.from_bounds( *bounds ).transformed(transform) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 49eee82679a0..8bb5545d8a7e 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -673,7 +673,7 @@ def set_figure(self, fig): super().set_figure(fig) self.bbox = mtransforms.TransformedBbox(self._position, - fig.transFigure) + fig.transSubfigure) # these will be updated later as data is added self.dataLim = mtransforms.Bbox.null() self._viewLim = mtransforms.Bbox.unit() @@ -1652,8 +1652,10 @@ def apply_aspect(self, position=None): self._set_position(position, which='active') return - fig_width, fig_height = self.get_figure().get_size_inches() - fig_aspect = fig_height / fig_width + trans = self.get_figure().transSubfigure + bb = mtransforms.Bbox.from_bounds(0, 0, 1, 1).transformed(trans) + # this is the physical aspect of the panel (or figure): + fig_aspect = bb.height / bb.width if self._adjustable == 'box': if self in self._twinned_axes: diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 04b3731d6d52..2e3d9a3100d0 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -3,6 +3,12 @@ `Figure` Top level `~matplotlib.artist.Artist`, which holds all plot elements. + Many methods are implemented in `FigureBase`. + +`SubFigure` + A logical figure inside a figure, usually added to a figure (or parent + `SubFigure`) with `Figure.add_subfigure` or `Figure.subfigures` methods + (provisional API v3.4). `SubplotParams` Control the default spacing between subplots. @@ -213,149 +219,23 @@ def update(self, left=None, bottom=None, right=None, top=None, self.hspace = hspace -class Figure(Artist): +class FigureBase(Artist): """ - The top level container for all the plot elements. - - The Figure instance supports callbacks through a *callbacks* attribute - which is a `.CallbackRegistry` instance. The events you can connect to - are 'dpi_changed', and the callback will be called with ``func(fig)`` where - fig is the `Figure` instance. - - Attributes - ---------- - patch - The `.Rectangle` instance representing the figure background patch. - - suppressComposite - For multiple figure images, the figure will make composite images - depending on the renderer option_image_nocomposite function. If - *suppressComposite* is a boolean, this will override the renderer. + Base class for `.figure.Figure` and `.figure.SubFigure` containing the + methods that add artists to the figure or subfigure, create axes, etc. """ - - def __str__(self): - return "Figure(%gx%g)" % tuple(self.bbox.size) - - def __repr__(self): - return "<{clsname} size {h:g}x{w:g} with {naxes} Axes>".format( - clsname=self.__class__.__name__, - h=self.bbox.size[0], w=self.bbox.size[1], - naxes=len(self.axes), - ) - - def __init__(self, - figsize=None, - dpi=None, - facecolor=None, - edgecolor=None, - linewidth=0.0, - frameon=None, - subplotpars=None, # rc figure.subplot.* - tight_layout=None, # rc figure.autolayout - constrained_layout=None, # rc figure.constrained_layout.use - ): - """ - Parameters - ---------- - figsize : 2-tuple of floats, default: :rc:`figure.figsize` - Figure dimension ``(width, height)`` in inches. - - dpi : float, default: :rc:`figure.dpi` - Dots per inch. - - facecolor : default: :rc:`figure.facecolor` - The figure patch facecolor. - - edgecolor : default: :rc:`figure.edgecolor` - The figure patch edge color. - - linewidth : float - The linewidth of the frame (i.e. the edge linewidth of the figure - patch). - - frameon : bool, default: :rc:`figure.frameon` - If ``False``, suppress drawing the figure background patch. - - subplotpars : `SubplotParams` - Subplot parameters. If not given, the default subplot - parameters :rc:`figure.subplot.*` are used. - - tight_layout : bool or dict, default: :rc:`figure.autolayout` - If ``False`` use *subplotpars*. If ``True`` adjust subplot - parameters using `.tight_layout` with default padding. - When providing a dict containing the keys ``pad``, ``w_pad``, - ``h_pad``, and ``rect``, the default `.tight_layout` paddings - will be overridden. - - constrained_layout : bool, default: :rc:`figure.constrained_layout.use` - If ``True`` use constrained layout to adjust positioning of plot - elements. Like ``tight_layout``, but designed to be more - flexible. See - :doc:`/tutorials/intermediate/constrainedlayout_guide` - for examples. (Note: does not work with `add_subplot` or - `~.pyplot.subplot2grid`.) - """ + def __init__(self): super().__init__() # remove the non-figure artist _axes property # as it makes no sense for a figure to be _in_ an axes # this is used by the property methods in the artist base class # which are over-ridden in this class del self._axes - self.callbacks = cbook.CallbackRegistry() - - if figsize is None: - figsize = mpl.rcParams['figure.figsize'] - if dpi is None: - dpi = mpl.rcParams['figure.dpi'] - if facecolor is None: - facecolor = mpl.rcParams['figure.facecolor'] - if edgecolor is None: - edgecolor = mpl.rcParams['figure.edgecolor'] - if frameon is None: - frameon = mpl.rcParams['figure.frameon'] - - if not np.isfinite(figsize).all() or (np.array(figsize) < 0).any(): - raise ValueError('figure size must be positive finite not ' - f'{figsize}') - self.bbox_inches = Bbox.from_bounds(0, 0, *figsize) - - self.dpi_scale_trans = Affine2D().scale(dpi) - # do not use property as it will trigger - self._dpi = dpi - self.bbox = TransformedBbox(self.bbox_inches, self.dpi_scale_trans) - - self.transFigure = BboxTransformTo(self.bbox) - - self.patch = Rectangle( - xy=(0, 0), width=1, height=1, visible=frameon, - facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, - # Don't let the figure patch influence bbox calculation. - in_layout=False) - self._set_artist_props(self.patch) - self.patch.set_antialiased(False) - FigureCanvasBase(self) # Set self.canvas. self._suptitle = None - if subplotpars is None: - subplotpars = SubplotParams() - - self.subplotpars = subplotpars - # constrained_layout: self._layoutgrid = None - self._constrained = False - - self.set_tight_layout(tight_layout) - - self._axstack = _AxesStack() # track all figure axes and current axes - self.clf() - self._cachedRenderer = None - - self.set_constrained_layout(constrained_layout) - # stub for subpanels: - self.panels = [] - self.transPanel = self.transFigure # groupers to keep track of x and y labels we want to align. # see self.align_xlabels and self.align_ylabels and @@ -363,285 +243,115 @@ def __init__(self, self._align_xlabel_grp = cbook.Grouper() self._align_ylabel_grp = cbook.Grouper() + self.figure = self # list of child gridspecs for this figure self._gridspecs = [] + self._localaxes = _AxesStack() # keep track of axes at this level + self.artists = [] + self.lines = [] + self.patches = [] + self.texts = [] + self.images = [] + self.legends = [] + self.subfigs = [] + self._suptitle = None + self.stale = True + self.suppressComposite = None - # TODO: I'd like to dynamically add the _repr_html_ method - # to the figure in the right context, but then IPython doesn't - # use it, for some reason. - - def _repr_html_(self): - # We can't use "isinstance" here, because then we'd end up importing - # webagg unconditionally. - if 'WebAgg' in type(self.canvas).__name__: - from matplotlib.backends import backend_webagg - return backend_webagg.ipython_inline_display(self) - - def show(self, warn=True): - """ - If using a GUI backend with pyplot, display the figure window. - - If the figure was not created using `~.pyplot.figure`, it will lack - a `~.backend_bases.FigureManagerBase`, and this method will raise an - AttributeError. - - .. warning:: + def _get_draw_artists(self, renderer): + """Also runs apply_aspect""" + artists = self.get_children() + for sfig in self.subfigs: + artists.remove(sfig) + childa = sfig.get_children() + for child in childa: + if child in artists: + artists.remove(child) - This does not manage an GUI event loop. Consequently, the figure - may only be shown briefly or not shown at all if you or your - environment are not managing an event loop. + artists.remove(self.patch) + artists = sorted( + (artist for artist in artists if not artist.get_animated()), + key=lambda artist: artist.get_zorder()) + for ax in self._localaxes.as_list(): + locator = ax.get_axes_locator() + if locator: + pos = locator(ax, renderer) + ax.apply_aspect(pos) + else: + ax.apply_aspect() - Proper use cases for `.Figure.show` include running this from a - GUI application or an IPython shell. + for child in ax.get_children(): + if hasattr(child, 'apply_aspect'): + locator = child.get_axes_locator() + if locator: + pos = locator(child, renderer) + child.apply_aspect(pos) + else: + child.apply_aspect() + return artists - If you're running a pure python shell or executing a non-GUI - python script, you should use `matplotlib.pyplot.show` instead, - which takes care of managing the event loop for you. + def autofmt_xdate( + self, bottom=0.2, rotation=30, ha='right', which='major'): + """ + 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. Parameters ---------- - warn : bool, default: True - If ``True`` and we are not running headless (i.e. on Linux with an - unset DISPLAY), issue warning when called on a non-GUI backend. + bottom : float, default: 0.2 + The bottom of the subplots for `subplots_adjust`. + rotation : float, default: 30 degrees + The rotation angle of the xtick labels in degrees. + ha : {'left', 'center', 'right'}, default: 'right' + The horizontal alignment of the xticklabels. + which : {'major', 'minor', 'both'}, default: 'major' + Selects which ticklabels to rotate. """ - if self.canvas.manager is None: - raise AttributeError( - "Figure.show works only for figures managed by pyplot, " - "normally created by pyplot.figure()") - try: - self.canvas.manager.show() - except NonGuiException as exc: - cbook._warn_external(str(exc)) + if which is None: + cbook.warn_deprecated( + "3.3", message="Support for passing which=None to mean " + "which='major' is deprecated since %(since)s and will be " + "removed %(removal)s.") + allsubplots = all(hasattr(ax, 'get_subplotspec') for ax in self.axes) + if len(self.axes) == 1: + for label in self.axes[0].get_xticklabels(which=which): + label.set_ha(ha) + label.set_rotation(rotation) + else: + if allsubplots: + for ax in self.get_axes(): + if ax.get_subplotspec().is_last_row(): + for label in ax.get_xticklabels(which=which): + label.set_ha(ha) + label.set_rotation(rotation) + else: + for label in ax.get_xticklabels(which=which): + label.set_visible(False) + ax.set_xlabel('') - def get_axes(self): - """ - Return a list of axes in the Figure. You can access and modify the - axes in the Figure through this list. + if allsubplots: + self.subplots_adjust(bottom=bottom) + self.stale = True - Do not modify the list itself. Instead, use `~Figure.add_axes`, - `~.Figure.add_subplot` or `~.Figure.delaxes` to add or remove an axes. + def get_children(self): + """Get a list of artists contained in the figure.""" + return [self.patch, + *self.artists, + *self._localaxes.as_list(), + *self.lines, + *self.patches, + *self.texts, + *self.images, + *self.legends, + *self.subfigs] - Note: This is equivalent to the property `~.Figure.axes`. + def contains(self, mouseevent): """ - return self._axstack.as_list() - - axes = property(get_axes, doc=""" - List of axes in the Figure. You can access and modify the axes in the - Figure through this list. - - Do not modify the list itself. Instead, use "`~Figure.add_axes`, - `~.Figure.add_subplot` or `~.Figure.delaxes` to add or remove an axes. - """) - - def _get_dpi(self): - return self._dpi - - def _set_dpi(self, dpi, forward=True): - """ - Parameters - ---------- - dpi : float - - forward : bool - Passed on to `~.Figure.set_size_inches` - """ - if dpi == self._dpi: - # We don't want to cause undue events in backends. - return - self._dpi = dpi - self.dpi_scale_trans.clear().scale(dpi) - w, h = self.get_size_inches() - self.set_size_inches(w, h, forward=forward) - self.callbacks.process('dpi_changed', self) - - dpi = property(_get_dpi, _set_dpi, doc="The resolution in dots per inch.") - - def get_tight_layout(self): - """Return whether `.tight_layout` is called when drawing.""" - return self._tight - - def set_tight_layout(self, tight): - """ - Set whether and how `.tight_layout` is called when drawing. - - Parameters - ---------- - tight : bool or dict with keys "pad", "w_pad", "h_pad", "rect" or None - If a bool, sets whether to call `.tight_layout` upon drawing. - If ``None``, use the ``figure.autolayout`` rcparam instead. - If a dict, pass it as kwargs to `.tight_layout`, overriding the - default paddings. - """ - if tight is None: - tight = mpl.rcParams['figure.autolayout'] - self._tight = bool(tight) - self._tight_parameters = tight if isinstance(tight, dict) else {} - self.stale = True - - def get_constrained_layout(self): - """ - Return whether constrained layout is being used. - - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. - """ - return self._constrained - - def set_constrained_layout(self, constrained): - """ - Set whether ``constrained_layout`` is used upon drawing. If None, - :rc:`figure.constrained_layout.use` value will be used. - - When providing a dict containing the keys `w_pad`, `h_pad` - the default ``constrained_layout`` paddings will be - overridden. These pads are in inches and default to 3.0/72.0. - ``w_pad`` is the width padding and ``h_pad`` is the height padding. - - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. - - Parameters - ---------- - constrained : bool or dict or None - """ - self._constrained_layout_pads = dict() - self._constrained_layout_pads['w_pad'] = None - self._constrained_layout_pads['h_pad'] = None - self._constrained_layout_pads['wspace'] = None - self._constrained_layout_pads['hspace'] = None - if constrained is None: - constrained = mpl.rcParams['figure.constrained_layout.use'] - self._constrained = bool(constrained) - if isinstance(constrained, dict): - self.set_constrained_layout_pads(**constrained) - else: - self.set_constrained_layout_pads() - - self.init_layoutgrid() - - self.stale = True - - def set_constrained_layout_pads(self, **kwargs): - """ - Set padding for ``constrained_layout``. Note the kwargs can be passed - as a dictionary ``fig.set_constrained_layout(**paddict)``. - - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. - - Parameters - ---------- - w_pad : float - Width padding in inches. This is the pad around axes - and is meant to make sure there is enough room for fonts to - look good. Defaults to 3 pts = 0.04167 inches - - h_pad : float - Height padding in inches. Defaults to 3 pts. - - wspace : float - Width padding between subplots, expressed as a fraction of the - subplot width. The total padding ends up being w_pad + wspace. - - hspace : float - Height padding between subplots, expressed as a fraction of the - subplot width. The total padding ends up being h_pad + hspace. - - """ - - todo = ['w_pad', 'h_pad', 'wspace', 'hspace'] - for td in todo: - if td in kwargs and kwargs[td] is not None: - self._constrained_layout_pads[td] = kwargs[td] - else: - self._constrained_layout_pads[td] = ( - mpl.rcParams['figure.constrained_layout.' + td]) - - def get_constrained_layout_pads(self, relative=False): - """ - Get padding for ``constrained_layout``. - - Returns a list of ``w_pad, h_pad`` in inches and - ``wspace`` and ``hspace`` as fractions of the subplot. - - See :doc:`/tutorials/intermediate/constrainedlayout_guide`. - - Parameters - ---------- - relative : bool - If `True`, then convert from inches to figure relative. - """ - w_pad = self._constrained_layout_pads['w_pad'] - h_pad = self._constrained_layout_pads['h_pad'] - wspace = self._constrained_layout_pads['wspace'] - hspace = self._constrained_layout_pads['hspace'] - - if relative and (w_pad is not None or h_pad is not None): - renderer0 = layoutgrid.get_renderer(self) - dpi = renderer0.dpi - w_pad = w_pad * dpi / renderer0.width - h_pad = h_pad * dpi / renderer0.height - - return w_pad, h_pad, wspace, hspace - - def autofmt_xdate( - self, bottom=0.2, rotation=30, ha='right', which='major'): - """ - 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. - - Parameters - ---------- - bottom : float, default: 0.2 - The bottom of the subplots for `subplots_adjust`. - rotation : float, default: 30 degrees - The rotation angle of the xtick labels in degrees. - ha : {'left', 'center', 'right'}, default: 'right' - The horizontal alignment of the xticklabels. - which : {'major', 'minor', 'both'}, default: 'major' - Selects which ticklabels to rotate. - """ - if which is None: - cbook.warn_deprecated( - "3.3", message="Support for passing which=None to mean " - "which='major' is deprecated since %(since)s and will be " - "removed %(removal)s.") - allsubplots = all(hasattr(ax, 'get_subplotspec') for ax in self.axes) - if len(self.axes) == 1: - for label in self.axes[0].get_xticklabels(which=which): - label.set_ha(ha) - label.set_rotation(rotation) - else: - if allsubplots: - for ax in self.get_axes(): - if ax.get_subplotspec().is_last_row(): - for label in ax.get_xticklabels(which=which): - label.set_ha(ha) - label.set_rotation(rotation) - else: - for label in ax.get_xticklabels(which=which): - label.set_visible(False) - ax.set_xlabel('') - - if allsubplots: - self.subplots_adjust(bottom=bottom) - self.stale = True - - def get_children(self): - """Get a list of artists contained in the figure.""" - return [self.patch, - *self.artists, - *self.axes, - *self.lines, - *self.patches, - *self.texts, - *self.images, - *self.legends] - - def contains(self, mouseevent): - """ - Test whether the mouse event occurred on the figure. + Test whether the mouse event occurred on the figure. Returns ------- @@ -733,213 +443,58 @@ def suptitle(self, t, **kwargs): sup.remove() else: self._suptitle = sup + if manual_position: self._suptitle.set_in_layout(False) + self.stale = True return self._suptitle - def set_canvas(self, canvas): + 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_frameon(self): """ - Set the canvas that contains the figure + Return the figure's background patch visibility, i.e. + whether the figure background will be drawn. Equivalent to + ``Figure.patch.get_visible()``. + """ + return self.patch.get_visible() + + def set_linewidth(self, linewidth): + """ + Set the line width of the Figure rectangle. Parameters ---------- - canvas : FigureCanvas + linewidth : number """ - self.canvas = canvas + self.patch.set_linewidth(linewidth) - def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, - vmin=None, vmax=None, origin=None, resize=False, **kwargs): + def get_linewidth(self): """ - Add a non-resampled image to the figure. + Get the line width of the Figure rectangle. + """ + return self.patch.get_linewidth() - The image is attached to the lower or upper left corner depending on - *origin*. + def set_edgecolor(self, color): + """ + Set the edge color of the Figure rectangle. Parameters ---------- - X - The image data. This is an array of one of the following shapes: + color : color + """ + self.patch.set_edgecolor(color) - - MxN: luminance (grayscale) values - - MxNx3: RGB values - - MxNx4: RGBA values - - xo, yo : int - The *x*/*y* image offset in pixels. - - alpha : None or float - The alpha blending value. - - norm : `matplotlib.colors.Normalize` - A `.Normalize` instance to map the luminance to the - interval [0, 1]. - - cmap : str or `matplotlib.colors.Colormap`, default: :rc:`image.cmap` - The colormap to use. - - vmin, vmax : float - If *norm* is not given, these values set the data limits for the - colormap. - - origin : {'upper', 'lower'}, default: :rc:`image.origin` - Indicates where the [0, 0] index of the array is in the upper left - or lower left corner of the axes. - - resize : bool - If *True*, resize the figure to match the given image size. - - Returns - ------- - `matplotlib.image.FigureImage` - - Other Parameters - ---------------- - **kwargs - Additional kwargs are `.Artist` kwargs passed on to `.FigureImage`. - - Notes - ----- - figimage complements the axes image (`~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 - `~matplotlib.axes.Axes` with extent [0, 0, 1, 1]. - - Examples - -------- - :: - - f = plt.figure() - nx = int(f.get_figwidth() * f.dpi) - ny = int(f.get_figheight() * f.dpi) - data = np.random.random((ny, nx)) - f.figimage(data) - plt.show() - """ - if resize: - dpi = self.get_dpi() - figsize = [x / dpi for x in (X.shape[1], X.shape[0])] - self.set_size_inches(figsize, forward=True) - - im = mimage.FigureImage(self, cmap, norm, xo, yo, origin, **kwargs) - im.stale_callback = _stale_figure_callback - - im.set_array(X) - im.set_alpha(alpha) - if norm is None: - im.set_clim(vmin, vmax) - self.images.append(im) - im._remove_method = self.images.remove - self.stale = True - return im - - def set_size_inches(self, w, h=None, forward=True): - """ - Set the figure size in inches. - - Call signatures:: - - fig.set_size_inches(w, h) # OR - fig.set_size_inches((w, h)) - - Parameters - ---------- - w : (float, float) or float - Width and height in inches (if height not specified as a separate - argument) or width. - h : float - Height in inches. - forward : bool, default: True - If ``True``, the canvas size is automatically updated, e.g., - you can resize the figure window from the shell. - - See Also - -------- - matplotlib.figure.Figure.get_size_inches - matplotlib.figure.Figure.set_figwidth - matplotlib.figure.Figure.set_figheight - - Notes - ----- - To transform from pixels to inches divide by `Figure.dpi`. - """ - if h is None: # Got called with a single pair as argument. - w, h = w - size = np.array([w, h]) - if not np.isfinite(size).all() or (size < 0).any(): - raise ValueError(f'figure size must be positive finite not {size}') - self.bbox_inches.p1 = size - if forward: - canvas = getattr(self, 'canvas') - if canvas is not None: - dpi_ratio = getattr(canvas, '_dpi_ratio', 1) - manager = getattr(canvas, 'manager', None) - if manager is not None: - manager.resize(*(size * self.dpi / dpi_ratio).astype(int)) - self.stale = True - - def get_size_inches(self): - """ - Return the current size of the figure in inches. - - Returns - ------- - ndarray - The size (width, height) of the figure in inches. - - See Also - -------- - matplotlib.figure.Figure.set_size_inches - matplotlib.figure.Figure.get_figwidth - matplotlib.figure.Figure.get_figheight - - Notes - ----- - The size in pixels can be obtained by multiplying with `Figure.dpi`. - """ - return np.array(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 figure width in inches.""" - return self.bbox_inches.width - - def get_figheight(self): - """Return the figure height in inches.""" - return self.bbox_inches.height - - def get_dpi(self): - """Return the resolution in dots per inch as a float.""" - return self.dpi - - def get_frameon(self): - """ - Return the figure's background patch visibility, i.e. - whether the figure background will be drawn. Equivalent to - ``Figure.patch.get_visible()``. - """ - return self.patch.get_visible() - - def set_edgecolor(self, color): - """ - Set the edge color of the Figure rectangle. - - Parameters - ---------- - color : color - """ - self.patch.set_edgecolor(color) - - def set_facecolor(self, color): - """ - Set the face color of the Figure rectangle. + def set_facecolor(self, color): + """ + Set the face color of the Figure rectangle. Parameters ---------- @@ -947,51 +502,6 @@ def set_facecolor(self, color): """ self.patch.set_facecolor(color) - def set_dpi(self, val): - """ - Set the resolution of the figure in dots-per-inch. - - Parameters - ---------- - val : float - """ - self.dpi = val - self.stale = True - - def set_figwidth(self, val, forward=True): - """ - Set the width of the figure in inches. - - Parameters - ---------- - val : float - forward : bool - See `set_size_inches`. - - See Also - -------- - matplotlib.figure.Figure.set_figheight - matplotlib.figure.Figure.set_size_inches - """ - self.set_size_inches(val, self.get_figheight(), forward=forward) - - def set_figheight(self, val, forward=True): - """ - Set the height of the figure in inches. - - Parameters - ---------- - val : float - forward : bool - See `set_size_inches`. - - See Also - -------- - matplotlib.figure.Figure.set_figwidth - matplotlib.figure.Figure.set_size_inches - """ - self.set_size_inches(self.get_figwidth(), val, forward=forward) - def set_frameon(self, b): """ Set the figure's background patch visibility, i.e. @@ -1020,7 +530,7 @@ def add_artist(self, artist, clip=False): artist : `~matplotlib.artist.Artist` The artist to add to the figure. If the added artist has no transform previously set, its transform will be set to - ``figure.transFigure``. + ``figure.transSubfigure``. clip : bool, default: False Whether the added artist should be clipped by the figure patch. @@ -1034,7 +544,7 @@ def add_artist(self, artist, clip=False): artist._remove_method = self.artists.remove if not artist.is_transform_set(): - artist.set_transform(self.transFigure) + artist.set_transform(self.transSubfigure) if clip: artist.set_clip_path(self.patch) @@ -1042,86 +552,15 @@ def add_artist(self, artist, clip=False): self.stale = True return artist - 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: - # some objects can define __getitem__ without being - # iterable and in those cases the conversion to tuples - # will fail. So instead of using the np.iterable(v) function - # we simply try and convert to a tuple, and proceed if not. - try: - v = tuple(v) - except Exception: - pass - ret.append((k, v)) - return tuple(ret) + @docstring.dedent_interpd + def add_axes(self, *args, **kwargs): + """ + Add an axes to the figure. - def fixlist(args): - ret = [] - for a in args: - if np.iterable(a): - a = tuple(a) - ret.append(a) - return tuple(ret) + Call signatures:: - key = fixlist(args), fixitems(kwargs.items()) - return key - - def _process_projection_requirements( - self, *args, axes_class=None, polar=False, projection=None, - **kwargs): - """ - Handle the args/kwargs to add_axes/add_subplot/gca, returning:: - - (axes_proj_class, proj_class_kwargs, proj_stack_key) - - which can be used for new axes initialization/identification. - """ - if axes_class is not None: - if polar or projection is not None: - raise ValueError( - "Cannot combine 'axes_class' and 'projection' or 'polar'") - projection_class = axes_class - else: - - if polar: - if projection is not None and projection != 'polar': - raise ValueError( - "polar=True, yet projection=%r. " - "Only one of these arguments should be supplied." % - projection) - projection = 'polar' - - if isinstance(projection, str) or projection is None: - projection_class = projections.get_projection_class(projection) - elif hasattr(projection, '_as_mpl_axes'): - projection_class, extra_kwargs = projection._as_mpl_axes() - kwargs.update(**extra_kwargs) - else: - raise TypeError( - f"projection must be a string, None or implement a " - f"_as_mpl_axes method, not {projection!r}") - - # Make the key without projection kwargs, this is used as a unique - # lookup for axes instances - key = self._make_key(*args, **kwargs) - - return projection_class, kwargs, key - - @docstring.dedent_interpd - def add_axes(self, *args, **kwargs): - """ - Add an axes to the figure. - - Call signatures:: - - add_axes(rect, projection=None, polar=False, **kwargs) - add_axes(ax) + add_axes(rect, projection=None, polar=False, **kwargs) + add_axes(ax) Parameters ---------- @@ -1251,7 +690,6 @@ def add_axes(self, *args, **kwargs): # create the new axes using the axes class given a = projection_class(self, rect, **kwargs) - return self._add_axes_internal(key, a) @docstring.dedent_interpd @@ -1408,13 +846,12 @@ def add_subplot(self, *args, **kwargs): # more similar to add_axes. self._axstack.remove(ax) ax = subplot_class_factory(projection_class)(self, *args, **kwargs) - return self._add_axes_internal(key, ax) def _add_axes_internal(self, key, ax): """Private helper for `add_axes` and `add_subplot`.""" - #self._localaxes += [ax] self._axstack.add(key, ax) + self._localaxes.add(key, ax) self.sca(ax) ax._remove_method = self.delaxes self.stale = True @@ -1528,209 +965,10 @@ def subplots(self, nrows=1, ncols=1, sharex=False, sharey=False, """ if gridspec_kw is None: gridspec_kw = {} - return (self.add_gridspec(nrows, ncols, figure=self, **gridspec_kw) - .subplots(sharex=sharex, sharey=sharey, squeeze=squeeze, - subplot_kw=subplot_kw)) - - @staticmethod - def _normalize_grid_string(layout): - layout = inspect.cleandoc(layout) - return [list(ln) for ln in layout.strip('\n').split('\n')] - - def subplot_mosaic(self, layout, *, subplot_kw=None, gridspec_kw=None, - empty_sentinel='.'): - """ - Build a layout of Axes based on ASCII art or nested lists. - - This is a helper function to build complex GridSpec layouts visually. - - .. note :: - - This API is provisional and may be revised in the future based on - early user feedback. - - - Parameters - ---------- - layout : list of list of {hashable or nested} or str - - A visual layout of how you want your Axes to be arranged - labeled as strings. For example :: - - x = [['A panel', 'A panel', 'edge'], - ['C panel', '.', 'edge']] - - Produces 4 axes: - - - 'A panel' which is 1 row high and spans the first two columns - - 'edge' which is 2 rows high and is on the right edge - - 'C panel' which in 1 row and 1 column wide in the bottom left - - a blank space 1 row and 1 column wide in the bottom center - - Any of the entries in the layout can be a list of lists - of the same form to create nested layouts. - - If input is a str, then it must be of the form :: - - ''' - AAE - C.E - ''' - - where each character is a column and each line is a row. - This only allows only single character Axes labels and does - not allow nesting but is very terse. - - subplot_kw : dict, optional - Dictionary with keywords passed to the `.Figure.add_subplot` call - used to create each subplot. - - gridspec_kw : dict, optional - Dictionary with keywords passed to the `.GridSpec` constructor used - to create the grid the subplots are placed on. - - empty_sentinel : object, optional - Entry in the layout to mean "leave this space empty". Defaults - to ``'.'``. Note, if *layout* is a string, it is processed via - `inspect.cleandoc` to remove leading white space, which may - interfere with using white-space as the empty sentinel. - - Returns - ------- - dict[label, Axes] - A dictionary mapping the labels to the Axes objects. - - """ - subplot_kw = subplot_kw or {} - gridspec_kw = gridspec_kw or {} - # special-case string input - if isinstance(layout, str): - layout = self._normalize_grid_string(layout) - - def _make_array(inp): - """ - Convert input into 2D array - - We need to have this internal function rather than - ``np.asarray(..., dtype=object)`` so that a list of lists - of lists does not get converted to an array of dimension > - 2 - - Returns - ------- - 2D object array - - """ - r0, *rest = inp - for j, r in enumerate(rest, start=1): - if isinstance(r, str): - raise ValueError('List layout specification must be 2D') - if len(r0) != len(r): - raise ValueError( - "All of the rows must be the same length, however " - f"the first row ({r0!r}) has length {len(r0)} " - f"and row {j} ({r!r}) has length {len(r)}." - ) - out = np.zeros((len(inp), len(r0)), dtype=object) - for j, r in enumerate(inp): - for k, v in enumerate(r): - out[j, k] = v - return out - - def _identify_keys_and_nested(layout): - """ - Given a 2D object array, identify unique IDs and nested layouts - - Parameters - ---------- - layout : 2D numpy object array - - Returns - ------- - unique_ids : Set[object] - The unique non-sub layout entries in this layout - nested : Dict[Tuple[int, int]], 2D object array - """ - unique_ids = set() - nested = {} - for j, row in enumerate(layout): - for k, v in enumerate(row): - if v == empty_sentinel: - continue - elif not cbook.is_scalar_or_string(v): - nested[(j, k)] = _make_array(v) - else: - unique_ids.add(v) - - return unique_ids, nested - - def _do_layout(gs, layout, unique_ids, nested): - """ - Recursively do the layout. - - Parameters - ---------- - gs : GridSpec - - layout : 2D object array - The input converted to a 2D numpy array for this level. - - unique_ids : Set[object] - The identified scalar labels at this level of nesting. - - nested : Dict[Tuple[int, int]], 2D object array - The identified nested layouts if any. - - Returns - ------- - Dict[label, Axes] - A flat dict of all of the Axes created. - """ - rows, cols = layout.shape - output = dict() - - # create the Axes at this level of nesting - for name in unique_ids: - indx = np.argwhere(layout == name) - start_row, start_col = np.min(indx, axis=0) - end_row, end_col = np.max(indx, axis=0) + 1 - slc = (slice(start_row, end_row), slice(start_col, end_col)) - - if (layout[slc] != name).any(): - raise ValueError( - f"While trying to layout\n{layout!r}\n" - f"we found that the label {name!r} specifies a " - "non-rectangular or non-contiguous area.") - - ax = self.add_subplot( - gs[slc], **{'label': str(name), **subplot_kw} - ) - output[name] = ax - - # do any sub-layouts - for (j, k), nested_layout in nested.items(): - rows, cols = nested_layout.shape - nested_output = _do_layout( - gs[j, k].subgridspec(rows, cols, **gridspec_kw), - nested_layout, - *_identify_keys_and_nested(nested_layout) - ) - overlap = set(output) & set(nested_output) - if overlap: - raise ValueError(f"There are duplicate keys {overlap} " - f"between the outer layout\n{layout!r}\n" - f"and the nested layout\n{nested_layout}") - output.update(nested_output) - return output - - layout = _make_array(layout) - rows, cols = layout.shape - gs = self.add_gridspec(rows, cols, **gridspec_kw) - ret = _do_layout(gs, layout, *_identify_keys_and_nested(layout)) - for k, ax in ret.items(): - if isinstance(k, str): - ax.set_label(k) - return ret + gs = self.add_gridspec(nrows, ncols, figure=self, **gridspec_kw) + axs = gs.subplots(sharex=sharex, sharey=sharey, squeeze=squeeze, + subplot_kw=subplot_kw) + return axs def delaxes(self, ax): """ @@ -1779,9 +1017,9 @@ def _break_share_link(ax, grouper): return None self._axstack.remove(ax) - # self._localaxes.remove(ax) self._axobservers.process("_axes_change_event", self) self.stale = True + self._localaxes.remove(ax) last_ax = _break_share_link(ax, ax._shared_y_axes) if last_ax is not None: @@ -1791,106 +1029,6 @@ def _break_share_link(ax, grouper): if last_ax is not None: _reset_locators_and_formatters(last_ax.xaxis) - 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 = cbook.CallbackRegistry() - self._suptitle = None - if self.get_constrained_layout(): - self.init_layoutgrid() - self.stale = True - - def clear(self, keep_observers=False): - """Clear the figure -- synonym for `clf`.""" - self.clf(keep_observers=keep_observers) - - @_finalize_rasterization - @allow_rasterization - def draw(self, renderer): - # docstring inherited - self._cachedRenderer = renderer - - # draw the figure bounding box, perhaps none for white figure - if not self.get_visible(): - return - - artists = self.get_children() - artists.remove(self.patch) - artists = sorted( - (artist for artist in artists if not artist.get_animated()), - key=lambda artist: artist.get_zorder()) - - for ax in self.axes: - locator = ax.get_axes_locator() - if locator: - pos = locator(ax, renderer) - ax.apply_aspect(pos) - else: - ax.apply_aspect() - - for child in ax.get_children(): - if hasattr(child, 'apply_aspect'): - locator = child.get_axes_locator() - if locator: - pos = locator(child, renderer) - child.apply_aspect(pos) - else: - child.apply_aspect() - - try: - renderer.open_group('figure', gid=self.get_gid()) - if self.get_constrained_layout() and self.axes: - self.execute_constrained_layout(renderer) - if self.get_tight_layout() and self.axes: - try: - self.tight_layout(**self._tight_parameters) - except ValueError: - pass - # ValueError can occur when resizing a window. - - self.patch.draw(renderer) - mimage._draw_list_compositing_images( - renderer, self, artists, self.suppressComposite) - - renderer.close_group('figure') - finally: - self.stale = False - - self.canvas.draw_event(renderer) - - def draw_artist(self, a): - """ - Draw `.Artist` instance *a* only. - - This can only be called after the figure has been drawn. - """ - if self._cachedRenderer is None: - raise AttributeError("draw_artist can only be used after an " - "initial draw which caches the renderer") - a.draw(self._cachedRenderer) - # Note: in the docstring below, the newlines in the examples after the # calls to legend() allow replacing it with figlegend() to generate the # docstring of pyplot.figlegend. @@ -2007,7 +1145,7 @@ def legend(self, *args, **kwargs): # kwargs['loc'] = extra_args[0] # extra_args = extra_args[1:] pass - transform = kwargs.pop('bbox_transform', self.transFigure) + transform = kwargs.pop('bbox_transform', self.transSubfigure) # explicitly set the bbox transform if the user hasn't. l = mlegend.Legend(self, handles, labels, *extra_args, bbox_transform=transform, **kwargs) @@ -2040,123 +1178,1683 @@ def text(self, x, y, s, fontdict=None, **kwargs): ------- `~.text.Text` - Other Parameters - ---------------- - **kwargs : `~matplotlib.text.Text` properties - Other miscellaneous text parameters. + Other Parameters + ---------------- + **kwargs : `~matplotlib.text.Text` properties + Other miscellaneous text parameters. + + %(Text)s + + See Also + -------- + .Axes.text + .pyplot.text + """ + effective_kwargs = { + 'transform': self.transSubfigure, + **(fontdict if fontdict is not None else {}), + **kwargs, + } + text = Text(x=x, y=y, text=s, **effective_kwargs) + text.set_figure(self) + text.stale_callback = _stale_figure_callback + + self.texts.append(text) + text._remove_method = self.texts.remove + self.stale = True + return text + + @docstring.dedent_interpd + def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw): + """%(colorbar_doc)s""" + if ax is None: + ax = self.gca() + if (hasattr(mappable, "axes") and ax is not mappable.axes + and cax is None): + cbook.warn_deprecated( + "3.4", message="Starting from Matplotlib 3.6, colorbar() " + "will steal space from the mappable's axes, rather than " + "from the current axes, to place the colorbar. To " + "silence this warning, explicitly pass the 'ax' argument " + "to colorbar().") + + # Store the value of gca so that we can set it back later on. + current_ax = self.gca() + if cax is None: + if (use_gridspec and isinstance(ax, SubplotBase) + and not self.get_constrained_layout()): + cax, kw = cbar.make_axes_gridspec(ax, **kw) + else: + cax, kw = cbar.make_axes(ax, **kw) + + # need to remove kws that cannot be passed to Colorbar + NON_COLORBAR_KEYS = ['fraction', 'pad', 'shrink', 'aspect', 'anchor', + 'panchor'] + cb_kw = {k: v for k, v in kw.items() if k not in NON_COLORBAR_KEYS} + cb = cbar.Colorbar(cax, mappable, **cb_kw) + + self.sca(current_ax) + self.stale = True + return cb + + def subplots_adjust(self, left=None, bottom=None, right=None, top=None, + wspace=None, hspace=None): + """ + Adjust the subplot layout parameters. + + Unset parameters are left unmodified; initial values are given by + :rc:`figure.subplot.[name]`. + + Parameters + ---------- + left : float, optional + The position of the left edge of the subplots, + as a fraction of the figure width. + right : float, optional + The position of the right edge of the subplots, + as a fraction of the figure width. + bottom : float, optional + The position of the bottom edge of the subplots, + as a fraction of the figure height. + top : float, optional + The position of the top edge of the subplots, + as a fraction of the figure height. + wspace : float, optional + The width of the padding between subplots, + as a fraction of the average axes width. + hspace : float, optional + The height of the padding between subplots, + as a fraction of the average axes height. + """ + if self.get_constrained_layout(): + self.set_constrained_layout(False) + cbook._warn_external( + "This figure was using constrained_layout, but that is " + "incompatible with subplots_adjust and/or tight_layout; " + "disabling constrained_layout.") + self.subplotpars.update(left, bottom, right, top, wspace, hspace) + for ax in self.axes: + if isinstance(ax, SubplotBase): + ax._set_position(ax.get_subplotspec().get_position(self)) + self.stale = True + + def align_xlabels(self, axs=None): + """ + Align the xlabels of subplots in the same subplot column if label + alignment is being done automatically (i.e. the label position is + not manually set). + + Alignment persists for draw events after this is called. + + If a label is on the bottom, it is aligned with labels on axes that + also have their label on the bottom and that have the same + bottom-most subplot row. If the label is on the top, + it is aligned with labels on axes with the same top-most row. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list of (or ndarray) `~matplotlib.axes.Axes` + to align the xlabels. + Default is to align all axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_labels + + Notes + ----- + This assumes that ``axs`` are from the same `.GridSpec`, so that + their `.SubplotSpec` positions correspond to figure positions. + + Examples + -------- + Example with rotated xtick labels:: + + fig, axs = plt.subplots(1, 2) + for tick in axs[0].get_xticklabels(): + tick.set_rotation(55) + axs[0].set_xlabel('XLabel 0') + axs[1].set_xlabel('XLabel 1') + fig.align_xlabels() + """ + if axs is None: + axs = self.axes + axs = np.ravel(axs) + for ax in axs: + _log.debug(' Working on: %s', ax.get_xlabel()) + rowspan = ax.get_subplotspec().rowspan + pos = ax.xaxis.get_label_position() # top or bottom + # Search through other axes for label positions that are same as + # this one and that share the appropriate row number. + # Add to a grouper associated with each axes of siblings. + # This list is inspected in `axis.draw` by + # `axis._update_label_position`. + for axc in axs: + if axc.xaxis.get_label_position() == pos: + rowspanc = axc.get_subplotspec().rowspan + if (pos == 'top' and rowspan.start == rowspanc.start or + pos == 'bottom' and rowspan.stop == rowspanc.stop): + # grouper for groups of xlabels to align + self._align_xlabel_grp.join(ax, axc) + + def align_ylabels(self, axs=None): + """ + Align the ylabels of subplots in the same subplot column if label + alignment is being done automatically (i.e. the label position is + not manually set). + + Alignment persists for draw events after this is called. + + If a label is on the left, it is aligned with labels on axes that + also have their label on the left and that have the same + left-most subplot column. If the label is on the right, + it is aligned with labels on axes with the same right-most column. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list (or ndarray) of `~matplotlib.axes.Axes` + to align the ylabels. + Default is to align all axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + matplotlib.figure.Figure.align_labels + + Notes + ----- + This assumes that ``axs`` are from the same `.GridSpec`, so that + their `.SubplotSpec` positions correspond to figure positions. + + Examples + -------- + Example with large yticks labels:: + + fig, axs = plt.subplots(2, 1) + axs[0].plot(np.arange(0, 1000, 50)) + axs[0].set_ylabel('YLabel 0') + axs[1].set_ylabel('YLabel 1') + fig.align_ylabels() + """ + if axs is None: + axs = self.axes + axs = np.ravel(axs) + for ax in axs: + _log.debug(' Working on: %s', ax.get_ylabel()) + colspan = ax.get_subplotspec().colspan + pos = ax.yaxis.get_label_position() # left or right + # Search through other axes for label positions that are same as + # this one and that share the appropriate column number. + # Add to a list associated with each axes of siblings. + # This list is inspected in `axis.draw` by + # `axis._update_label_position`. + for axc in axs: + if axc.yaxis.get_label_position() == pos: + colspanc = axc.get_subplotspec().colspan + if (pos == 'left' and colspan.start == colspanc.start or + pos == 'right' and colspan.stop == colspanc.stop): + # grouper for groups of ylabels to align + self._align_ylabel_grp.join(ax, axc) + + def align_labels(self, axs=None): + """ + Align the xlabels and ylabels of subplots with the same subplots + row or column (respectively) if label alignment is being + done automatically (i.e. the label position is not manually set). + + Alignment persists for draw events after this is called. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list (or ndarray) of `~matplotlib.axes.Axes` + to align the labels. + Default is to align all axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + + matplotlib.figure.Figure.align_ylabels + """ + self.align_xlabels(axs=axs) + self.align_ylabels(axs=axs) + + def add_gridspec(self, nrows=1, ncols=1, **kwargs): + """ + Return a `.GridSpec` that has this figure as a parent. This allows + complex layout of axes in the figure. + + Parameters + ---------- + nrows : int, default: 1 + Number of rows in grid. + + ncols : int, default: 1 + Number or columns in grid. + + Returns + ------- + `.GridSpec` + + Other Parameters + ---------------- + **kwargs + Keyword arguments are passed to `.GridSpec`. + + See Also + -------- + matplotlib.pyplot.subplots + + Examples + -------- + Adding a subplot that spans two rows:: + + fig = plt.figure() + gs = fig.add_gridspec(2, 2) + ax1 = fig.add_subplot(gs[0, 0]) + ax2 = fig.add_subplot(gs[1, 0]) + # spans two rows: + ax3 = fig.add_subplot(gs[:, 1]) + + """ + + _ = kwargs.pop('figure', None) # pop in case user has added this... + gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, **kwargs) + self._gridspecs.append(gs) + return gs + + def subfigures(self, nrows=1, ncols=1, squeeze=True, + wspace=None, hspace=None, + width_ratios=None, height_ratios=None, + **kwargs): + """ + Add a subfigure to this figure or subfigure. + + A subfigure has the same artist methods as a figure, and is logically + the same as a figure, but cannot print itself. + See :doc:`/gallery/subplots_axes_and_figures/subfigures`. + + Parameters + ---------- + nrows, ncols : int, default: 1 + Number of rows/columns of the subfigure grid. + + squeeze : bool, default: True + If True, extra dimensions are squeezed out from the returned + array of subfigures. + + wspace, hspace : float, default: None + The amount of width/height reserved for space between subfigures, + expressed as a fraction of the average subfigure width/height. + If not given, the values will be inferred from a figure or + rcParams when necessary. + + width_ratios : array-like of length *ncols*, optional + Defines the relative widths of the columns. Each column gets a + relative width of ``width_ratios[i] / sum(width_ratios)``. + If not given, all columns will have the same width. + + height_ratios : array-like of length *nrows*, optional + Defines the relative heights of the rows. Each row gets a + relative height of ``height_ratios[i] / sum(height_ratios)``. + If not given, all rows will have the same height. + """ + gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, + wspace=wspace, hspace=hspace, + width_ratios=width_ratios, + height_ratios=height_ratios) + + sfarr = np.empty((nrows, ncols), dtype=object) + for i in range(ncols): + for j in range(nrows): + sfarr[j, i] = self.add_subfigure(gs[j, i], **kwargs) + + if squeeze: + # Discarding unneeded dimensions that equal 1. If we only have one + # subfigure, just return it instead of a 1-element array. + return sfarr.item() if sfarr.size == 1 else sfarr.squeeze() + else: + # Returned axis array will be always 2-d, even if nrows=ncols=1. + return sfarr + + return sfarr + + def add_subfigure(self, subplotspec, **kwargs): + """ + Add a `~.figure.SubFigure` to the figure as part of a subplot + arrangement. + + Parameters + ---------- + subplotspec : `.gridspec.SubplotSpec` + Defines the region in a parent gridspec where the subfigure will + be placed. + + Returns + ------- + `.figure.SubFigure` + + Other Parameters + ---------------- + **kwargs + Are passed to the `~.figure.SubFigure` object. + + See Also + -------- + .Figure.subfigures + """ + sf = SubFigure(self, subplotspec, **kwargs) + self.subfigs += [sf] + return sf + + def sca(self, a): + """Set the current axes to be *a* and return *a*.""" + self._axstack.bubble(a) + self._axobservers.process("_axes_change_event", self) + return a + + @docstring.dedent_interpd + def gca(self, **kwargs): + """ + Get 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 matches + # 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: + projection_class, _, key = \ + self._process_projection_requirements(**kwargs) + + # 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 + else: + cbook._warn_external('Requested projection is different ' + 'from current axis projection, ' + 'creating new axis with requested ' + 'projection.') + + # no axes found, so create one which spans the figure + return self.add_subplot(1, 1, 1, **kwargs) + + def _gci(self): + # Helper for `~matplotlib.pyplot.gci`. Do not use elsewhere. + """ + Get the current colorable artist. + + Specifically, returns the current `.ScalarMappable` instance (`.Image` + created by `imshow` or `figimage`, `.Collection` created by `pcolor` or + `scatter`, etc.), or *None* if no such instance has been defined. + + The current image is an attribute of the current axes, or the nearest + earlier axes in the current figure that contains an image. + + Notes + ----- + Historically, the only colorable artists were images; hence the name + ``gci`` (get current image). + """ + # Look first for an image in the current Axes: + cax = self._axstack.current_key_axes()[1] + if cax is None: + return None + im = cax._gci() + if im is not None: + return im + + # If there is no image in the current Axes, search for + # one in a previously created Axes. Whether this makes + # sense is debatable, but it is the documented behavior. + for ax in reversed(self.axes): + im = ax._gci() + if im is not None: + return im + return None + + def _process_projection_requirements( + self, *args, axes_class=None, polar=False, projection=None, + **kwargs): + """ + Handle the args/kwargs to add_axes/add_subplot/gca, returning:: + + (axes_proj_class, proj_class_kwargs, proj_stack_key) + + which can be used for new axes initialization/identification. + """ + if axes_class is not None: + if polar or projection is not None: + raise ValueError( + "Cannot combine 'axes_class' and 'projection' or 'polar'") + projection_class = axes_class + else: + + if polar: + if projection is not None and projection != 'polar': + raise ValueError( + "polar=True, yet projection=%r. " + "Only one of these arguments should be supplied." % + projection) + projection = 'polar' + + if isinstance(projection, str) or projection is None: + projection_class = projections.get_projection_class(projection) + elif hasattr(projection, '_as_mpl_axes'): + projection_class, extra_kwargs = projection._as_mpl_axes() + kwargs.update(**extra_kwargs) + else: + raise TypeError( + f"projection must be a string, None or implement a " + f"_as_mpl_axes method, not {projection!r}") + + # Make the key without projection kwargs, this is used as a unique + # lookup for axes instances + key = self._make_key(*args, **kwargs) + + return projection_class, kwargs, key + + 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: + # some objects can define __getitem__ without being + # iterable and in those cases the conversion to tuples + # will fail. So instead of using the np.iterable(v) function + # we simply try and convert to a tuple, and proceed if not. + try: + v = tuple(v) + except Exception: + pass + ret.append((k, v)) + return tuple(ret) + + def fixlist(args): + ret = [] + for a in args: + if np.iterable(a): + a = tuple(a) + ret.append(a) + return tuple(ret) + + key = fixlist(args), fixitems(kwargs.items()) + return key + + def get_default_bbox_extra_artists(self): + bbox_artists = [artist for artist in self.get_children() + if (artist.get_visible() and artist.get_in_layout())] + for ax in self.axes: + if ax.get_visible(): + bbox_artists.extend(ax.get_default_bbox_extra_artists()) + return bbox_artists + + def get_tightbbox(self, renderer, bbox_extra_artists=None): + """ + Return a (tight) bounding box of the figure in inches. + + Artists that have ``artist.set_in_layout(False)`` are not included + in the bbox. + + Parameters + ---------- + renderer : `.RendererBase` subclass + renderer that will be used to draw the figures (i.e. + ``fig.canvas.get_renderer()``) + + bbox_extra_artists : list of `.Artist` or ``None`` + List of artists to include in the tight bounding box. If + ``None`` (default), then all artist children of each axes are + included in the tight bounding box. + + Returns + ------- + `.BboxBase` + containing the bounding box (in figure inches). + """ + + bb = [] + if bbox_extra_artists is None: + artists = self.get_default_bbox_extra_artists() + else: + artists = bbox_extra_artists + + for a in artists: + bbox = a.get_tightbbox(renderer) + if bbox is not None and (bbox.width != 0 or bbox.height != 0): + bb.append(bbox) + + for ax in self.axes: + if ax.get_visible(): + # some axes don't take the bbox_extra_artists kwarg so we + # need this conditional.... + try: + bbox = ax.get_tightbbox( + renderer, bbox_extra_artists=bbox_extra_artists) + except TypeError: + bbox = ax.get_tightbbox(renderer) + bb.append(bbox) + bb = [b for b in bb + if (np.isfinite(b.width) and np.isfinite(b.height) + and (b.width != 0 or b.height != 0))] + + if len(bb) == 0: + return self.bbox_inches + + _bbox = Bbox.union(bb) + + bbox_inches = TransformedBbox(_bbox, Affine2D().scale(1 / self.dpi)) + + return bbox_inches + + @staticmethod + def _normalize_grid_string(layout): + layout = inspect.cleandoc(layout) + return [list(ln) for ln in layout.strip('\n').split('\n')] + + def subplot_mosaic(self, layout, *, subplot_kw=None, gridspec_kw=None, + empty_sentinel='.'): + """ + Build a layout of Axes based on ASCII art or nested lists. + + This is a helper function to build complex GridSpec layouts visually. + + .. note :: + + This API is provisional and may be revised in the future based on + early user feedback. + + + Parameters + ---------- + layout : list of list of {hashable or nested} or str + + A visual layout of how you want your Axes to be arranged + labeled as strings. For example :: + + x = [['A panel', 'A panel', 'edge'], + ['C panel', '.', 'edge']] + + Produces 4 axes: + + - 'A panel' which is 1 row high and spans the first two columns + - 'edge' which is 2 rows high and is on the right edge + - 'C panel' which in 1 row and 1 column wide in the bottom left + - a blank space 1 row and 1 column wide in the bottom center + + Any of the entries in the layout can be a list of lists + of the same form to create nested layouts. + + If input is a str, then it must be of the form :: + + ''' + AAE + C.E + ''' + + where each character is a column and each line is a row. + This only allows only single character Axes labels and does + not allow nesting but is very terse. + + subplot_kw : dict, optional + Dictionary with keywords passed to the `.Figure.add_subplot` call + used to create each subplot. + + gridspec_kw : dict, optional + Dictionary with keywords passed to the `.GridSpec` constructor used + to create the grid the subplots are placed on. + + empty_sentinel : object, optional + Entry in the layout to mean "leave this space empty". Defaults + to ``'.'``. Note, if *layout* is a string, it is processed via + `inspect.cleandoc` to remove leading white space, which may + interfere with using white-space as the empty sentinel. + + Returns + ------- + dict[label, Axes] + A dictionary mapping the labels to the Axes objects. + + """ + subplot_kw = subplot_kw or {} + gridspec_kw = gridspec_kw or {} + # special-case string input + if isinstance(layout, str): + layout = self._normalize_grid_string(layout) + + def _make_array(inp): + """ + Convert input into 2D array + + We need to have this internal function rather than + ``np.asarray(..., dtype=object)`` so that a list of lists + of lists does not get converted to an array of dimension > + 2 + + Returns + ------- + 2D object array + + """ + r0, *rest = inp + for j, r in enumerate(rest, start=1): + if isinstance(r, str): + raise ValueError('List layout specification must be 2D') + if len(r0) != len(r): + raise ValueError( + "All of the rows must be the same length, however " + f"the first row ({r0!r}) has length {len(r0)} " + f"and row {j} ({r!r}) has length {len(r)}." + ) + out = np.zeros((len(inp), len(r0)), dtype=object) + for j, r in enumerate(inp): + for k, v in enumerate(r): + out[j, k] = v + return out + + def _identify_keys_and_nested(layout): + """ + Given a 2D object array, identify unique IDs and nested layouts + + Parameters + ---------- + layout : 2D numpy object array + + Returns + ------- + unique_ids : Set[object] + The unique non-sub layout entries in this layout + nested : Dict[Tuple[int, int]], 2D object array + """ + unique_ids = set() + nested = {} + for j, row in enumerate(layout): + for k, v in enumerate(row): + if v == empty_sentinel: + continue + elif not cbook.is_scalar_or_string(v): + nested[(j, k)] = _make_array(v) + else: + unique_ids.add(v) + + return unique_ids, nested + + def _do_layout(gs, layout, unique_ids, nested): + """ + Recursively do the layout. + + Parameters + ---------- + gs : GridSpec + + layout : 2D object array + The input converted to a 2D numpy array for this level. + + unique_ids : Set[object] + The identified scalar labels at this level of nesting. + + nested : Dict[Tuple[int, int]], 2D object array + The identified nested layouts if any. + + Returns + ------- + Dict[label, Axes] + A flat dict of all of the Axes created. + """ + rows, cols = layout.shape + output = dict() + + # create the Axes at this level of nesting + for name in unique_ids: + indx = np.argwhere(layout == name) + start_row, start_col = np.min(indx, axis=0) + end_row, end_col = np.max(indx, axis=0) + 1 + slc = (slice(start_row, end_row), slice(start_col, end_col)) + + if (layout[slc] != name).any(): + raise ValueError( + f"While trying to layout\n{layout!r}\n" + f"we found that the label {name!r} specifies a " + "non-rectangular or non-contiguous area.") + + ax = self.add_subplot( + gs[slc], **{'label': str(name), **subplot_kw} + ) + output[name] = ax + + # do any sub-layouts + for (j, k), nested_layout in nested.items(): + rows, cols = nested_layout.shape + nested_output = _do_layout( + gs[j, k].subgridspec(rows, cols, **gridspec_kw), + nested_layout, + *_identify_keys_and_nested(nested_layout) + ) + overlap = set(output) & set(nested_output) + if overlap: + raise ValueError(f"There are duplicate keys {overlap} " + f"between the outer layout\n{layout!r}\n" + f"and the nested layout\n{nested_layout}") + output.update(nested_output) + return output + + layout = _make_array(layout) + rows, cols = layout.shape + gs = self.add_gridspec(rows, cols, **gridspec_kw) + ret = _do_layout(gs, layout, *_identify_keys_and_nested(layout)) + for k, ax in ret.items(): + if isinstance(k, str): + ax.set_label(k) + return ret + + def _set_artist_props(self, a): + if a != self: + a.set_figure(self) + a.stale_callback = _stale_figure_callback + a.set_transform(self.transSubfigure) + + +class SubFigure(FigureBase): + """ + Logical figure that can be placed inside a figure. + + Typically instantiated using `.Figure.add_subfigure` or + `.SubFigure.add_subfigure`, or `.SubFigure.subfigures`. A subfigure has + the same methods as a figure except for those particularly tied to the size + or dpi of the figure, and is confined to a prescribed region of the figure. + For example the following puts two subfigures side-by-side:: + + fig = plt.figure() + sfigs = fig.subfigures(1, 2) + axsL = sfigs[0].subplots(1, 2) + axsR = sfigs[1].subplots(2, 1) + + See :doc:`/gallery/subplots_axes_and_figures/subfigures` + """ + + def __init__(self, parent, subplotspec, *, + facecolor=None, + edgecolor=None, + linewidth=0.0, + frameon=None): + """ + Parameters + ---------- + parent : `.figure.Figure` or `.figure.SubFigure` + Figure or subfigure that contains the SubFigure. SubFigures + can be nested. + + subplotspec : `.gridspec.SubplotSpec` + Defines the region in a parent gridspec where the subfigure will + be placed. + + facecolor : default: :rc:`figure.facecolor` + The figure patch face color. + + edgecolor : default: :rc:`figure.edgecolor` + The figure patch edge color. + + linewidth : float + The linewidth of the frame (i.e. the edge linewidth of the figure + patch). + + frameon : bool, default: :rc:`figure.frameon` + If ``False``, suppress drawing the figure background patch. + """ + super().__init__() + if facecolor is None: + facecolor = mpl.rcParams['figure.facecolor'] + if edgecolor is None: + edgecolor = mpl.rcParams['figure.edgecolor'] + if frameon is None: + frameon = mpl.rcParams['figure.frameon'] + + self._subplotspec = subplotspec + self._parent = parent + self.figure = parent.figure + # subfigures use the parent axstack + self._axstack = parent._axstack + self.subplotpars = parent.subplotpars + self.dpi_scale_trans = parent.dpi_scale_trans + self._axobservers = parent._axobservers + self.dpi = parent.dpi + self.canvas = parent.canvas + self.transFigure = parent.transFigure + self.bbox_relative = None + self._redo_transform_rel_fig() + self.figbbox = self._parent.figbbox + self.bbox = TransformedBbox(self.bbox_relative, + self._parent.transSubfigure) + self.transSubfigure = BboxTransformTo(self.bbox) + + self.patch = Rectangle( + xy=(0, 0), width=1, height=1, visible=frameon, + facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, + # Don't let the figure patch influence bbox calculation. + in_layout=False, transform=self.transSubfigure) + self._set_artist_props(self.patch) + self.patch.set_antialiased(False) + + if parent._layoutgrid is not None: + self.init_layoutgrid() + + def _redo_transform_rel_fig(self, bbox=None): + """ + Make the transSubfigure bbox relative to Figure transform. + + Parameters + ---------- + bbox : bbox or None + If not None, then the bbox is used for relative bounding box. + Otherwise it is calculated from the subplotspec. + """ + + if bbox is not None: + self.bbox_relative.p0 = bbox.p0 + self.bbox_relative.p1 = bbox.p1 + return + + gs = self._subplotspec.get_gridspec() + # need to figure out *where* this subplotspec is. + wr = gs.get_width_ratios() + hr = gs.get_height_ratios() + nrows, ncols = gs.get_geometry() + if wr is None: + wr = np.ones(ncols) + else: + wr = np.array(wr) + if hr is None: + hr = np.ones(nrows) + else: + hr = np.array(hr) + widthf = np.sum(wr[self._subplotspec.colspan]) / np.sum(wr) + heightf = np.sum(hr[self._subplotspec.rowspan]) / np.sum(hr) + + x0 = 0 + if not self._subplotspec.is_first_col(): + x0 += np.sum(wr[self._subplotspec.colspan.start - 1]) / np.sum(wr) + + y0 = 0 + if not self._subplotspec.is_last_row(): + y0 += 1 - (np.sum(hr[self._subplotspec.rowspan.stop - 1]) / + np.sum(hr)) + + if self.bbox_relative is None: + self.bbox_relative = Bbox.from_bounds(x0, y0, widthf, heightf) + else: + self.bbox_relative.p0 = (x0, y0) + self.bbox_relative.p1 = (x0 + widthf, y0 + heightf) + + def get_constrained_layout(self): + """ + Return whether constrained layout is being used. + + See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + """ + return self._parent.get_constrained_layout() + + def get_constrained_layout_pads(self, relative=False): + """ + Get padding for ``constrained_layout``. + + Returns a list of ``w_pad, h_pad`` in inches and + ``wspace`` and ``hspace`` as fractions of the subplot. + + See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + + Parameters + ---------- + relative : bool + If `True`, then convert from inches to figure relative. + """ + return self._parent.get_constrained_layout_pads(relative=relative) + + def init_layoutgrid(self): + """Initialize the layoutgrid for use in constrained_layout.""" + if self._layoutgrid is None: + gs = self._subplotspec.get_gridspec() + parent = gs._layoutgrid + if parent is not None: + self._layoutgrid = layoutgrid.LayoutGrid( + parent=parent, + name=(parent.name + '.' + 'panellb' + + layoutgrid.seq_id()), + parent_inner=True, + nrows=1, ncols=1, + parent_pos=(self._subplotspec.rowspan, + self._subplotspec.colspan)) + + def get_axes(self): + """ + Return a list of axes in the SubFigure. You can access and modify the + axes in the Figure through this list. + + Do not modify the list itself. Instead, use `~.SubFigure.add_axes`, + `~.SubFigure.add_subplot` or `~.SubFigure.delaxes` to add or remove an + axes. + + Note: This is equivalent to the property `~.SubFigure.axes`. + """ + return self._localaxes.as_list() + + axes = property(get_axes, doc=""" + List of axes in the SubFigure. You can access and modify the axes + in the SubFigure through this list. + + Do not modify the list itself. Instead, use `~.SubFigure.add_axes`, + `~.SubFigure.add_subplot` or `~.SubFigure.delaxes` to add or remove an + axes. + """) + + def draw(self, renderer): + # docstring inherited + self._cachedRenderer = renderer + + # draw the figure bounding box, perhaps none for white figure + if not self.get_visible(): + return + + artists = self._get_draw_artists(renderer) + + try: + renderer.open_group('subfigure', gid=self.get_gid()) + self.patch.draw(renderer) + mimage._draw_list_compositing_images(renderer, self, artists) + for sfig in self.subfigs: + sfig.draw(renderer) + renderer.close_group('subfigure') + + finally: + self.stale = False + + +class Figure(FigureBase): + """ + The top level container for all the plot elements. + + The Figure instance supports callbacks through a *callbacks* attribute + which is a `.CallbackRegistry` instance. The events you can connect to + are 'dpi_changed', and the callback will be called with ``func(fig)`` where + fig is the `Figure` instance. + + Attributes + ---------- + patch + The `.Rectangle` instance representing the figure background patch. + + suppressComposite + For multiple figure images, the figure will make composite images + depending on the renderer option_image_nocomposite function. If + *suppressComposite* is a boolean, this will override the renderer. + """ + + def __str__(self): + return "Figure(%gx%g)" % tuple(self.bbox.size) + + def __repr__(self): + return "<{clsname} size {h:g}x{w:g} with {naxes} Axes>".format( + clsname=self.__class__.__name__, + h=self.bbox.size[0], w=self.bbox.size[1], + naxes=len(self.axes), + ) + + def __init__(self, + figsize=None, + dpi=None, + facecolor=None, + edgecolor=None, + linewidth=0.0, + frameon=None, + subplotpars=None, # rc figure.subplot.* + tight_layout=None, # rc figure.autolayout + constrained_layout=None, # rc figure.constrained_layout.use + ): + """ + Parameters + ---------- + figsize : 2-tuple of floats, default: :rc:`figure.figsize` + Figure dimension ``(width, height)`` in inches. + + dpi : float, default: :rc:`figure.dpi` + Dots per inch. + + facecolor : default: :rc:`figure.facecolor` + The figure patch facecolor. + + edgecolor : default: :rc:`figure.edgecolor` + The figure patch edge color. + + linewidth : float + The linewidth of the frame (i.e. the edge linewidth of the figure + patch). + + frameon : bool, default: :rc:`figure.frameon` + If ``False``, suppress drawing the figure background patch. + + subplotpars : `SubplotParams` + Subplot parameters. If not given, the default subplot + parameters :rc:`figure.subplot.*` are used. + + tight_layout : bool or dict, default: :rc:`figure.autolayout` + If ``False`` use *subplotpars*. If ``True`` adjust subplot + parameters using `.tight_layout` with default padding. + When providing a dict containing the keys ``pad``, ``w_pad``, + ``h_pad``, and ``rect``, the default `.tight_layout` paddings + will be overridden. + + constrained_layout : bool, default: :rc:`figure.constrained_layout.use` + If ``True`` use constrained layout to adjust positioning of plot + elements. Like ``tight_layout``, but designed to be more + flexible. See + :doc:`/tutorials/intermediate/constrainedlayout_guide` + for examples. (Note: does not work with `add_subplot` or + `~.pyplot.subplot2grid`.) + """ + super().__init__() + + self.callbacks = cbook.CallbackRegistry() + + if figsize is None: + figsize = mpl.rcParams['figure.figsize'] + if dpi is None: + dpi = mpl.rcParams['figure.dpi'] + if facecolor is None: + facecolor = mpl.rcParams['figure.facecolor'] + if edgecolor is None: + edgecolor = mpl.rcParams['figure.edgecolor'] + if frameon is None: + frameon = mpl.rcParams['figure.frameon'] + + if not np.isfinite(figsize).all() or (np.array(figsize) < 0).any(): + raise ValueError('figure size must be positive finite not ' + f'{figsize}') + self.bbox_inches = Bbox.from_bounds(0, 0, *figsize) + + self.dpi_scale_trans = Affine2D().scale(dpi) + # do not use property as it will trigger + self._dpi = dpi + self.bbox = TransformedBbox(self.bbox_inches, self.dpi_scale_trans) + self.figbbox = self.bbox + self.transFigure = BboxTransformTo(self.bbox) + self.transSubfigure = self.transFigure + + self.patch = Rectangle( + xy=(0, 0), width=1, height=1, visible=frameon, + facecolor=facecolor, edgecolor=edgecolor, linewidth=linewidth, + # Don't let the figure patch influence bbox calculation. + in_layout=False) + self._set_artist_props(self.patch) + self.patch.set_antialiased(False) + + FigureCanvasBase(self) # Set self.canvas. + + if subplotpars is None: + subplotpars = SubplotParams() + + self.subplotpars = subplotpars + + # constrained_layout: + self._layoutgrid = None + self._constrained = False + + self.set_tight_layout(tight_layout) + + self._axstack = _AxesStack() # track all figure axes and current axes + self.clf() + self._cachedRenderer = None + + self.set_constrained_layout(constrained_layout) + + # groupers to keep track of x and y labels we want to align. + # see self.align_xlabels and self.align_ylabels and + # axis._get_tick_boxes_siblings + self._align_xlabel_grp = cbook.Grouper() + self._align_ylabel_grp = cbook.Grouper() + + # list of child gridspecs for this figure + self._gridspecs = [] + + # TODO: I'd like to dynamically add the _repr_html_ method + # to the figure in the right context, but then IPython doesn't + # use it, for some reason. + + def _repr_html_(self): + # We can't use "isinstance" here, because then we'd end up importing + # webagg unconditionally. + if 'WebAgg' in type(self.canvas).__name__: + from matplotlib.backends import backend_webagg + return backend_webagg.ipython_inline_display(self) + + def show(self, warn=True): + """ + If using a GUI backend with pyplot, display the figure window. + + If the figure was not created using `~.pyplot.figure`, it will lack + a `~.backend_bases.FigureManagerBase`, and this method will raise an + AttributeError. + + .. warning:: + + This does not manage an GUI event loop. Consequently, the figure + may only be shown briefly or not shown at all if you or your + environment are not managing an event loop. + + Proper use cases for `.Figure.show` include running this from a + GUI application or an IPython shell. + + If you're running a pure python shell or executing a non-GUI + python script, you should use `matplotlib.pyplot.show` instead, + which takes care of managing the event loop for you. + + Parameters + ---------- + warn : bool, default: True + If ``True`` and we are not running headless (i.e. on Linux with an + unset DISPLAY), issue warning when called on a non-GUI backend. + """ + if self.canvas.manager is None: + raise AttributeError( + "Figure.show works only for figures managed by pyplot, " + "normally created by pyplot.figure()") + try: + self.canvas.manager.show() + except NonGuiException as exc: + cbook._warn_external(str(exc)) + + def get_axes(self): + """ + Return a list of axes in the Figure. You can access and modify the + axes in the Figure through this list. + + Do not modify the list itself. Instead, use `~Figure.add_axes`, + `~.Figure.add_subplot` or `~.Figure.delaxes` to add or remove an axes. + + Note: This is equivalent to the property `~.Figure.axes`. + """ + return self._axstack.as_list() + + axes = property(get_axes, doc=""" + List of axes in the Figure. You can access and modify the axes in the + Figure through this list. + + Do not modify the list itself. Instead, use "`~Figure.add_axes`, + `~.Figure.add_subplot` or `~.Figure.delaxes` to add or remove an axes. + """) + + def _get_dpi(self): + return self._dpi + + def _set_dpi(self, dpi, forward=True): + """ + Parameters + ---------- + dpi : float + + forward : bool + Passed on to `~.Figure.set_size_inches` + """ + if dpi == self._dpi: + # We don't want to cause undue events in backends. + return + self._dpi = dpi + self.dpi_scale_trans.clear().scale(dpi) + w, h = self.get_size_inches() + self.set_size_inches(w, h, forward=forward) + self.callbacks.process('dpi_changed', self) + + dpi = property(_get_dpi, _set_dpi, doc="The resolution in dots per inch.") + + def get_tight_layout(self): + """Return whether `.tight_layout` is called when drawing.""" + return self._tight + + def set_tight_layout(self, tight): + """ + Set whether and how `.tight_layout` is called when drawing. + + Parameters + ---------- + tight : bool or dict with keys "pad", "w_pad", "h_pad", "rect" or None + If a bool, sets whether to call `.tight_layout` upon drawing. + If ``None``, use the ``figure.autolayout`` rcparam instead. + If a dict, pass it as kwargs to `.tight_layout`, overriding the + default paddings. + """ + if tight is None: + tight = mpl.rcParams['figure.autolayout'] + self._tight = bool(tight) + self._tight_parameters = tight if isinstance(tight, dict) else {} + self.stale = True + + def get_constrained_layout(self): + """ + Return whether constrained layout is being used. + + See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + """ + return self._constrained + + def set_constrained_layout(self, constrained): + """ + Set whether ``constrained_layout`` is used upon drawing. If None, + :rc:`figure.constrained_layout.use` value will be used. + + When providing a dict containing the keys `w_pad`, `h_pad` + the default ``constrained_layout`` paddings will be + overridden. These pads are in inches and default to 3.0/72.0. + ``w_pad`` is the width padding and ``h_pad`` is the height padding. + + See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + + Parameters + ---------- + constrained : bool or dict or None + """ + self._constrained_layout_pads = dict() + self._constrained_layout_pads['w_pad'] = None + self._constrained_layout_pads['h_pad'] = None + self._constrained_layout_pads['wspace'] = None + self._constrained_layout_pads['hspace'] = None + if constrained is None: + constrained = mpl.rcParams['figure.constrained_layout.use'] + self._constrained = bool(constrained) + if isinstance(constrained, dict): + self.set_constrained_layout_pads(**constrained) + else: + self.set_constrained_layout_pads() + + self.init_layoutgrid() + + self.stale = True + + def set_constrained_layout_pads(self, **kwargs): + """ + Set padding for ``constrained_layout``. Note the kwargs can be passed + as a dictionary ``fig.set_constrained_layout(**paddict)``. + + See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + + Parameters + ---------- + w_pad : float + Width padding in inches. This is the pad around axes + and is meant to make sure there is enough room for fonts to + look good. Defaults to 3 pts = 0.04167 inches + + h_pad : float + Height padding in inches. Defaults to 3 pts. + + wspace : float + Width padding between subplots, expressed as a fraction of the + subplot width. The total padding ends up being w_pad + wspace. + + hspace : float + Height padding between subplots, expressed as a fraction of the + subplot width. The total padding ends up being h_pad + hspace. + + """ + + todo = ['w_pad', 'h_pad', 'wspace', 'hspace'] + for td in todo: + if td in kwargs and kwargs[td] is not None: + self._constrained_layout_pads[td] = kwargs[td] + else: + self._constrained_layout_pads[td] = ( + mpl.rcParams['figure.constrained_layout.' + td]) + + def get_constrained_layout_pads(self, relative=False): + """ + Get padding for ``constrained_layout``. + + Returns a list of ``w_pad, h_pad`` in inches and + ``wspace`` and ``hspace`` as fractions of the subplot. + + See :doc:`/tutorials/intermediate/constrainedlayout_guide`. + + Parameters + ---------- + relative : bool + If `True`, then convert from inches to figure relative. + """ + w_pad = self._constrained_layout_pads['w_pad'] + h_pad = self._constrained_layout_pads['h_pad'] + wspace = self._constrained_layout_pads['wspace'] + hspace = self._constrained_layout_pads['hspace'] + + if relative and (w_pad is not None or h_pad is not None): + renderer0 = layoutgrid.get_renderer(self) + dpi = renderer0.dpi + w_pad = w_pad * dpi / renderer0.width + h_pad = h_pad * dpi / renderer0.height + + return w_pad, h_pad, wspace, hspace + + def set_canvas(self, canvas): + """ + Set the canvas that contains the figure + + Parameters + ---------- + canvas : FigureCanvas + """ + self.canvas = canvas + + def figimage(self, X, xo=0, yo=0, alpha=None, norm=None, cmap=None, + vmin=None, vmax=None, origin=None, resize=False, **kwargs): + """ + Add a non-resampled image to the figure. + + The image is attached to the lower or upper left corner depending on + *origin*. + + Parameters + ---------- + X + The image data. This is an array of one of the following shapes: + + - MxN: luminance (grayscale) values + - MxNx3: RGB values + - MxNx4: RGBA values + + xo, yo : int + The *x*/*y* image offset in pixels. + + alpha : None or float + The alpha blending value. + + norm : `matplotlib.colors.Normalize` + A `.Normalize` instance to map the luminance to the + interval [0, 1]. + + cmap : str or `matplotlib.colors.Colormap`, default: :rc:`image.cmap` + The colormap to use. + + vmin, vmax : float + If *norm* is not given, these values set the data limits for the + colormap. + + origin : {'upper', 'lower'}, default: :rc:`image.origin` + Indicates where the [0, 0] index of the array is in the upper left + or lower left corner of the axes. + + resize : bool + If *True*, resize the figure to match the given image size. + + Returns + ------- + `matplotlib.image.FigureImage` + + Other Parameters + ---------------- + **kwargs + Additional kwargs are `.Artist` kwargs passed on to `.FigureImage`. + + Notes + ----- + figimage complements the axes image (`~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 + `~matplotlib.axes.Axes` with extent [0, 0, 1, 1]. + + Examples + -------- + :: + + f = plt.figure() + nx = int(f.get_figwidth() * f.dpi) + ny = int(f.get_figheight() * f.dpi) + data = np.random.random((ny, nx)) + f.figimage(data) + plt.show() + """ + if resize: + dpi = self.get_dpi() + figsize = [x / dpi for x in (X.shape[1], X.shape[0])] + self.set_size_inches(figsize, forward=True) + + im = mimage.FigureImage(self, cmap, norm, xo, yo, origin, **kwargs) + im.stale_callback = _stale_figure_callback + + im.set_array(X) + im.set_alpha(alpha) + if norm is None: + im.set_clim(vmin, vmax) + self.images.append(im) + im._remove_method = self.images.remove + self.stale = True + return im + + def set_size_inches(self, w, h=None, forward=True): + """ + Set the figure size in inches. + + Call signatures:: + + fig.set_size_inches(w, h) # OR + fig.set_size_inches((w, h)) + + Parameters + ---------- + w : (float, float) or float + Width and height in inches (if height not specified as a separate + argument) or width. + h : float + Height in inches. + forward : bool, default: True + If ``True``, the canvas size is automatically updated, e.g., + you can resize the figure window from the shell. + + See Also + -------- + matplotlib.figure.Figure.get_size_inches + matplotlib.figure.Figure.set_figwidth + matplotlib.figure.Figure.set_figheight + + Notes + ----- + To transform from pixels to inches divide by `Figure.dpi`. + """ + if h is None: # Got called with a single pair as argument. + w, h = w + size = np.array([w, h]) + if not np.isfinite(size).all() or (size < 0).any(): + raise ValueError(f'figure size must be positive finite not {size}') + self.bbox_inches.p1 = size + if forward: + canvas = getattr(self, 'canvas') + if canvas is not None: + dpi_ratio = getattr(canvas, '_dpi_ratio', 1) + manager = getattr(canvas, 'manager', None) + if manager is not None: + manager.resize(*(size * self.dpi / dpi_ratio).astype(int)) + self.stale = True + + def get_size_inches(self): + """ + Return the current size of the figure in inches. + + Returns + ------- + ndarray + The size (width, height) of the figure in inches. + + See Also + -------- + matplotlib.figure.Figure.set_size_inches + matplotlib.figure.Figure.get_figwidth + matplotlib.figure.Figure.get_figheight + + Notes + ----- + The size in pixels can be obtained by multiplying with `Figure.dpi`. + """ + return np.array(self.bbox_inches.p1) + + def get_figwidth(self): + """Return the figure width in inches.""" + return self.bbox_inches.width + + def get_figheight(self): + """Return the figure height in inches.""" + return self.bbox_inches.height + + def get_dpi(self): + """Return the resolution in dots per inch as a float.""" + return self.dpi + + def set_dpi(self, val): + """ + Set the resolution of the figure in dots-per-inch. + + Parameters + ---------- + val : float + """ + self.dpi = val + self.stale = True + + def set_figwidth(self, val, forward=True): + """ + Set the width of the figure in inches. - %(Text)s + Parameters + ---------- + val : float + forward : bool + See `set_size_inches`. See Also -------- - .Axes.text - .pyplot.text + matplotlib.figure.Figure.set_figheight + matplotlib.figure.Figure.set_size_inches """ - effective_kwargs = { - 'transform': self.transFigure, - **(fontdict if fontdict is not None else {}), - **kwargs, - } - text = Text(x=x, y=y, text=s, **effective_kwargs) - text.set_figure(self) - text.stale_callback = _stale_figure_callback + self.set_size_inches(val, self.get_figheight(), forward=forward) - self.texts.append(text) - text._remove_method = self.texts.remove - self.stale = True - return text + def set_figheight(self, val, forward=True): + """ + Set the height of the figure in inches. - def _set_artist_props(self, a): - if a != self: - a.set_figure(self) - a.stale_callback = _stale_figure_callback - a.set_transform(self.transFigure) + Parameters + ---------- + val : float + forward : bool + See `set_size_inches`. - @docstring.dedent_interpd - def gca(self, **kwargs): + See Also + -------- + matplotlib.figure.Figure.set_figwidth + matplotlib.figure.Figure.set_size_inches """ - Get 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: + self.set_size_inches(self.get_figwidth(), val, forward=forward) - %(Axes)s + 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. """ - ckey, cax = self._axstack.current_key_axes() - # if there exists an axes on the stack see if it matches - # the desired axes configuration - if cax is not None: + self.suppressComposite = None + self.callbacks = cbook.CallbackRegistry() - # 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 + for ax in tuple(self.axes): # Iterate over the copy. + ax.cla() + self.delaxes(ax) # removes ax from self._axstack - # if the user has specified particular projection detail - # then build up a key which can represent this - else: - projection_class, _, key = \ - self._process_projection_requirements(**kwargs) + 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 = cbook.CallbackRegistry() + self._suptitle = None + if self.get_constrained_layout(): + self.init_layoutgrid() + self.stale = True - # let the returned axes have any gridspec by removing it from - # the key - ckey = ckey[1:] - key = key[1:] + def clear(self, keep_observers=False): + """Clear the figure -- synonym for `clf`.""" + self.clf(keep_observers=keep_observers) - # 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 - else: - cbook._warn_external('Requested projection is different ' - 'from current axis projection, ' - 'creating new axis with requested ' - 'projection.') + @_finalize_rasterization + @allow_rasterization + def draw(self, renderer): + # docstring inherited + self._cachedRenderer = renderer - # no axes found, so create one which spans the figure - return self.add_subplot(1, 1, 1, **kwargs) + # draw the figure bounding box, perhaps none for white figure + if not self.get_visible(): + return - def sca(self, a): - """Set the current axes to be *a* and return *a*.""" - self._axstack.bubble(a) - self._axobservers.process("_axes_change_event", self) - return a + artists = self._get_draw_artists(renderer) - def _gci(self): - # Helper for `~matplotlib.pyplot.gci`. Do not use elsewhere. - """ - Get the current colorable artist. + try: + renderer.open_group('figure', gid=self.get_gid()) + if self.get_constrained_layout() and self.axes: + self.execute_constrained_layout(renderer) + if self.get_tight_layout() and self.axes: + try: + self.tight_layout(**self._tight_parameters) + except ValueError: + pass + # ValueError can occur when resizing a window. - Specifically, returns the current `.ScalarMappable` instance (`.Image` - created by `imshow` or `figimage`, `.Collection` created by `pcolor` or - `scatter`, etc.), or *None* if no such instance has been defined. + self.patch.draw(renderer) + mimage._draw_list_compositing_images( + renderer, self, artists, self.suppressComposite) - The current image is an attribute of the current axes, or the nearest - earlier axes in the current figure that contains an image. + for sfig in self.subfigs: + sfig.draw(renderer) - Notes - ----- - Historically, the only colorable artists were images; hence the name - ``gci`` (get current image). + renderer.close_group('figure') + finally: + self.stale = False + + self.canvas.draw_event(renderer) + + def draw_artist(self, a): """ - # Look first for an image in the current Axes: - cax = self._axstack.current_key_axes()[1] - if cax is None: - return None - im = cax._gci() - if im is not None: - return im + Draw `.Artist` instance *a* only. - # If there is no image in the current Axes, search for - # one in a previously created Axes. Whether this makes - # sense is debatable, but it is the documented behavior. - for ax in reversed(self.axes): - im = ax._gci() - if im is not None: - return im - return None + This can only be called after the figure has been drawn. + """ + if self._cachedRenderer is None: + raise AttributeError("draw_artist can only be used after an " + "initial draw which caches the renderer") + a.draw(self._cachedRenderer) def __getstate__(self): state = super().__getstate__() @@ -2364,82 +3062,7 @@ def savefig(self, fname, *, transparent=None, **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, use_gridspec=True, **kw): - """%(colorbar_doc)s""" - if ax is None: - ax = self.gca() - if (hasattr(mappable, "axes") and ax is not mappable.axes - and cax is None): - cbook.warn_deprecated( - "3.4", message="Starting from Matplotlib 3.6, colorbar() " - "will steal space from the mappable's axes, rather than " - "from the current axes, to place the colorbar. To " - "silence this warning, explicitly pass the 'ax' argument " - "to colorbar().") - - # Store the value of gca so that we can set it back later on. - current_ax = self.gca() - - if cax is None: - if (use_gridspec and isinstance(ax, SubplotBase) - and not self.get_constrained_layout()): - cax, kw = cbar.make_axes_gridspec(ax, **kw) - else: - cax, kw = cbar.make_axes(ax, **kw) - - # need to remove kws that cannot be passed to Colorbar - NON_COLORBAR_KEYS = ['fraction', 'pad', 'shrink', 'aspect', 'anchor', - 'panchor'] - cb_kw = {k: v for k, v in kw.items() if k not in NON_COLORBAR_KEYS} - cb = cbar.Colorbar(cax, mappable, **cb_kw) - - self.sca(current_ax) - self.stale = True - return cb - - def subplots_adjust(self, left=None, bottom=None, right=None, top=None, - wspace=None, hspace=None): - """ - Adjust the subplot layout parameters. - - Unset parameters are left unmodified; initial values are given by - :rc:`figure.subplot.[name]`. - - Parameters - ---------- - left : float, optional - The position of the left edge of the subplots, - as a fraction of the figure width. - right : float, optional - The position of the right edge of the subplots, - as a fraction of the figure width. - bottom : float, optional - The position of the bottom edge of the subplots, - as a fraction of the figure height. - top : float, optional - The position of the top edge of the subplots, - as a fraction of the figure height. - wspace : float, optional - The width of the padding between subplots, - as a fraction of the average axes width. - hspace : float, optional - The height of the padding between subplots, - as a fraction of the average axes height. - """ - if self.get_constrained_layout(): - self.set_constrained_layout(False) - cbook._warn_external( - "This figure was using constrained_layout, but that is " - "incompatible with subplots_adjust and/or tight_layout; " - "disabling constrained_layout.") - self.subplotpars.update(left, bottom, right, top, wspace, hspace) - for ax in self.axes: - if isinstance(ax, SubplotBase): - ax._set_position(ax.get_subplotspec().get_position(self)) - self.stale = True + ax.patch.set_edgecolor(cc[1]) def ginput(self, n=1, timeout=30, show_clicks=True, mouse_add=MouseButton.LEFT, @@ -2508,72 +3131,6 @@ def waitforbuttonpress(self, timeout=-1): blocking_input = BlockingKeyMouseInput(self) return blocking_input(timeout=timeout) - def get_default_bbox_extra_artists(self): - bbox_artists = [artist for artist in self.get_children() - if (artist.get_visible() and artist.get_in_layout())] - for ax in self.axes: - if ax.get_visible(): - bbox_artists.extend(ax.get_default_bbox_extra_artists()) - return bbox_artists - - def get_tightbbox(self, renderer, bbox_extra_artists=None): - """ - Return a (tight) bounding box of the figure in inches. - - Artists that have ``artist.set_in_layout(False)`` are not included - in the bbox. - - Parameters - ---------- - renderer : `.RendererBase` subclass - renderer that will be used to draw the figures (i.e. - ``fig.canvas.get_renderer()``) - - bbox_extra_artists : list of `.Artist` or ``None`` - List of artists to include in the tight bounding box. If - ``None`` (default), then all artist children of each axes are - included in the tight bounding box. - - Returns - ------- - `.BboxBase` - containing the bounding box (in figure inches). - """ - - bb = [] - if bbox_extra_artists is None: - artists = self.get_default_bbox_extra_artists() - else: - artists = bbox_extra_artists - - for a in artists: - bbox = a.get_tightbbox(renderer) - if bbox is not None and (bbox.width != 0 or bbox.height != 0): - bb.append(bbox) - - for ax in self.axes: - if ax.get_visible(): - # some axes don't take the bbox_extra_artists kwarg so we - # need this conditional.... - try: - bbox = ax.get_tightbbox( - renderer, bbox_extra_artists=bbox_extra_artists) - except TypeError: - bbox = ax.get_tightbbox(renderer) - bb.append(bbox) - bb = [b for b in bb - if (np.isfinite(b.width) and np.isfinite(b.height) - and (b.width != 0 or b.height != 0))] - - if len(bb) == 0: - return self.bbox_inches - - _bbox = Bbox.union(bb) - - bbox_inches = TransformedBbox(_bbox, Affine2D().scale(1 / self.dpi)) - - return bbox_inches - def init_layoutgrid(self): """Initialize the layoutgrid for use in constrained_layout.""" del(self._layoutgrid) @@ -2660,195 +3217,6 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, if kwargs: self.subplots_adjust(**kwargs) - def align_xlabels(self, axs=None): - """ - Align the xlabels of subplots in the same subplot column if label - alignment is being done automatically (i.e. the label position is - not manually set). - - Alignment persists for draw events after this is called. - - If a label is on the bottom, it is aligned with labels on axes that - also have their label on the bottom and that have the same - bottom-most subplot row. If the label is on the top, - it is aligned with labels on axes with the same top-most row. - - Parameters - ---------- - axs : list of `~matplotlib.axes.Axes` - Optional list of (or ndarray) `~matplotlib.axes.Axes` - to align the xlabels. - Default is to align all axes on the figure. - - See Also - -------- - matplotlib.figure.Figure.align_ylabels - matplotlib.figure.Figure.align_labels - - Notes - ----- - This assumes that ``axs`` are from the same `.GridSpec`, so that - their `.SubplotSpec` positions correspond to figure positions. - - Examples - -------- - Example with rotated xtick labels:: - - fig, axs = plt.subplots(1, 2) - for tick in axs[0].get_xticklabels(): - tick.set_rotation(55) - axs[0].set_xlabel('XLabel 0') - axs[1].set_xlabel('XLabel 1') - fig.align_xlabels() - """ - if axs is None: - axs = self.axes - axs = np.ravel(axs) - for ax in axs: - _log.debug(' Working on: %s', ax.get_xlabel()) - rowspan = ax.get_subplotspec().rowspan - pos = ax.xaxis.get_label_position() # top or bottom - # Search through other axes for label positions that are same as - # this one and that share the appropriate row number. - # Add to a grouper associated with each axes of siblings. - # This list is inspected in `axis.draw` by - # `axis._update_label_position`. - for axc in axs: - if axc.xaxis.get_label_position() == pos: - rowspanc = axc.get_subplotspec().rowspan - if (pos == 'top' and rowspan.start == rowspanc.start or - pos == 'bottom' and rowspan.stop == rowspanc.stop): - # grouper for groups of xlabels to align - self._align_xlabel_grp.join(ax, axc) - - def align_ylabels(self, axs=None): - """ - Align the ylabels of subplots in the same subplot column if label - alignment is being done automatically (i.e. the label position is - not manually set). - - Alignment persists for draw events after this is called. - - If a label is on the left, it is aligned with labels on axes that - also have their label on the left and that have the same - left-most subplot column. If the label is on the right, - it is aligned with labels on axes with the same right-most column. - - Parameters - ---------- - axs : list of `~matplotlib.axes.Axes` - Optional list (or ndarray) of `~matplotlib.axes.Axes` - to align the ylabels. - Default is to align all axes on the figure. - - See Also - -------- - matplotlib.figure.Figure.align_xlabels - matplotlib.figure.Figure.align_labels - - Notes - ----- - This assumes that ``axs`` are from the same `.GridSpec`, so that - their `.SubplotSpec` positions correspond to figure positions. - - Examples - -------- - Example with large yticks labels:: - - fig, axs = plt.subplots(2, 1) - axs[0].plot(np.arange(0, 1000, 50)) - axs[0].set_ylabel('YLabel 0') - axs[1].set_ylabel('YLabel 1') - fig.align_ylabels() - """ - if axs is None: - axs = self.axes - axs = np.ravel(axs) - for ax in axs: - _log.debug(' Working on: %s', ax.get_ylabel()) - colspan = ax.get_subplotspec().colspan - pos = ax.yaxis.get_label_position() # left or right - # Search through other axes for label positions that are same as - # this one and that share the appropriate column number. - # Add to a list associated with each axes of siblings. - # This list is inspected in `axis.draw` by - # `axis._update_label_position`. - for axc in axs: - if axc.yaxis.get_label_position() == pos: - colspanc = axc.get_subplotspec().colspan - if (pos == 'left' and colspan.start == colspanc.start or - pos == 'right' and colspan.stop == colspanc.stop): - # grouper for groups of ylabels to align - self._align_ylabel_grp.join(ax, axc) - - def align_labels(self, axs=None): - """ - Align the xlabels and ylabels of subplots with the same subplots - row or column (respectively) if label alignment is being - done automatically (i.e. the label position is not manually set). - - Alignment persists for draw events after this is called. - - Parameters - ---------- - axs : list of `~matplotlib.axes.Axes` - Optional list (or ndarray) of `~matplotlib.axes.Axes` - to align the labels. - Default is to align all axes on the figure. - - See Also - -------- - matplotlib.figure.Figure.align_xlabels - - matplotlib.figure.Figure.align_ylabels - """ - self.align_xlabels(axs=axs) - self.align_ylabels(axs=axs) - - def add_gridspec(self, nrows=1, ncols=1, **kwargs): - """ - Return a `.GridSpec` that has this figure as a parent. This allows - complex layout of axes in the figure. - - Parameters - ---------- - nrows : int, default: 1 - Number of rows in grid. - - ncols : int, default: 1 - Number or columns in grid. - - Returns - ------- - `.GridSpec` - - Other Parameters - ---------------- - **kwargs - Keyword arguments are passed to `.GridSpec`. - - See Also - -------- - matplotlib.pyplot.subplots - - Examples - -------- - Adding a subplot that spans two rows:: - - fig = plt.figure() - gs = fig.add_gridspec(2, 2) - ax1 = fig.add_subplot(gs[0, 0]) - ax2 = fig.add_subplot(gs[1, 0]) - # spans two rows: - ax3 = fig.add_subplot(gs[:, 1]) - - """ - - _ = kwargs.pop('figure', None) # pop in case user has added this... - gs = GridSpec(nrows=nrows, ncols=ncols, figure=self, **kwargs) - self._gridspecs.append(gs) - return gs - def figaspect(arg): """ diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 8dcaea286db5..fa2b0da16448 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -4276,34 +4276,43 @@ def __init__(self, xyA, xyB, coordsA, coordsB=None, *coordsA* and *coordsB* are strings that indicate the coordinates of *xyA* and *xyB*. - ================= =================================================== - Property Description - ================= =================================================== - 'figure points' points from the lower left corner of the figure - 'figure pixels' pixels from the lower left corner of the figure - 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper right - 'axes points' points from lower left corner of axes - 'axes pixels' pixels from lower left corner of axes - 'axes fraction' 0, 0 is lower left of axes and 1, 1 is upper right - 'data' use the coordinate system of the object being - annotated (default) - 'offset points' offset (in points) from the *xy* value - 'polar' you can specify *theta*, *r* for the annotation, - even in cartesian plots. Note that if you are using - a polar axes, you do not need to specify polar for - the coordinate system since that is the native - "data" coordinate system. - ================= =================================================== + ==================== ================================================== + Property Description + ==================== ================================================== + 'figure points' points from the lower left corner of the figure + 'figure pixels' pixels from the lower left corner of the figure + 'figure fraction' 0, 0 is lower left of figure and 1, 1 is upper + right + 'subfigure points' points from the lower left corner of the subfigure + 'subfigure pixels' pixels from the lower left corner of the subfigure + 'subfigure fraction' fraction of the subfigure, 0, 0 is lower left. + 'axes points' points from lower left corner of axes + 'axes pixels' pixels from lower left corner of axes + 'axes fraction' 0, 0 is lower left of axes and 1, 1 is upper right + 'data' use the coordinate system of the object being + annotated (default) + 'offset points' offset (in points) from the *xy* value + 'polar' you can specify *theta*, *r* for the annotation, + even in cartesian plots. Note that if you are + using a polar axes, you do not need to specify + polar for the coordinate system since that is the + native "data" coordinate system. + ==================== ================================================== Alternatively they can be set to any valid `~matplotlib.transforms.Transform`. + Note that 'subfigure pixels' and 'figure pixels' are the same + for the parent figure, so users who want code that is usable in + a subfigure can use 'subfigure pixels'. + .. note:: Using `ConnectionPatch` across two `~.axes.Axes` instances is not directly compatible with :doc:`constrained layout `. Add the artist - directly to the `.Figure` instead of adding it to a specific Axes. + directly to the `.Figure` instead of adding it to a specific Axes, + or exclude it from the layout using ``con.set_in_layout(False)``. .. code-block:: default @@ -4348,6 +4357,8 @@ def _get_xy(self, xy, s, axes=None): s = s.replace("points", "pixels") elif s == "figure fraction": s = self.figure.transFigure + elif s == "subfigure fraction": + s = self.figure.transSubfigure elif s == "axes fraction": s = axes.transAxes x, y = xy @@ -4370,6 +4381,12 @@ def _get_xy(self, xy, s, axes=None): trans = axes.transData return trans.transform((x, y)) elif s == 'figure pixels': + # pixels from the lower left corner of the figure + bb = self.figure.figbbox + x = bb.x0 + x if x >= 0 else bb.x1 + x + y = bb.y0 + y if y >= 0 else bb.y1 + y + return x, y + elif s == 'subfigure pixels': # pixels from the lower left corner of the figure bb = self.figure.bbox x = bb.x0 + x if x >= 0 else bb.x1 + x diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index e177d727d142..527aac59b6ed 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1068,8 +1068,8 @@ def subplot(*args, **kwargs): """ Add a subplot to the current figure. - Wrapper of `.Figure.add_subplot` with a difference in behavior - explained in the notes section. + Wrapper of `.Figure.add_subplot` with a difference in + behavior explained in the notes section. Call signatures:: @@ -1159,7 +1159,7 @@ def subplot(*args, **kwargs): two subplots that are otherwise identical to be added to the figure, make sure you give them unique labels. - In rare circumstances, `.add_subplot` may be called with a single + In rare circumstances, `.Figure.add_subplot` may be called with a single argument, a subplot axes instance already created in the present figure but not in the figure's list of axes. diff --git a/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure.png b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure.png new file mode 100644 index 0000000000000000000000000000000000000000..2e7e43ec6ba05d36a8c53a010d94e623c9b080d3 GIT binary patch literal 62512 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 YeK8-+vbD#Lgo1ynmozSBT`=?i7fcY{e*gdg literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_ss.png b/lib/matplotlib/tests/baseline_images/test_figure/test_subfigure_ss.png new file mode 100644 index 0000000000000000000000000000000000000000..62342ccf95910b2422a933bfe8118fc08f2b910b GIT binary patch literal 52726 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

Kux&PuetqsRJY(N+-j^M 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 RJ_z_H`$0*vRN~XO{|9S&oWB47 literal 0 HcmV?d00001 diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index fbddcfe7876d..ab90a96792bd 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -810,3 +810,50 @@ def test_reused_gridspec(): assert gs1 == gs2 assert gs1 == gs3 + + +@image_comparison(['test_subfigure.png'], style='mpl20', + savefig_kwarg={'facecolor': 'teal'}, + remove_text=False) +def test_subfigure(): + np.random.seed(19680808) + fig = plt.figure(constrained_layout=True) + sub = fig.subfigures(1, 2) + + axs = sub[0].subplots(2, 2) + for ax in axs.flat: + pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2, vmax=2) + sub[0].colorbar(pc, ax=axs) + sub[0].suptitle('Left Side') + + axs = sub[1].subplots(1, 3) + for ax in axs.flat: + pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2, vmax=2) + sub[1].colorbar(pc, ax=axs, location='bottom') + sub[1].suptitle('Right Side') + + fig.suptitle('Figure suptitle', fontsize='xx-large') + + +@image_comparison(['test_subfigure_ss.png'], style='mpl20', + savefig_kwarg={'facecolor': 'teal'}, + remove_text=False) +def test_subfigure_ss(): + # test assigning the subfigure via subplotspec + np.random.seed(19680808) + fig = plt.figure(constrained_layout=True) + gs = fig.add_gridspec(1, 2) + + sub = fig.add_subfigure(gs[0], facecolor='pink') + + axs = sub.subplots(2, 2) + for ax in axs.flat: + pc = ax.pcolormesh(np.random.randn(30, 30), vmin=-2, vmax=2) + sub.colorbar(pc, ax=axs) + sub.suptitle('Left Side') + + ax = fig.add_subplot(gs[1]) + ax.plot(np.arange(20)) + ax.set_title('Axes') + + fig.suptitle('Figure suptitle', fontsize='xx-large') diff --git a/lib/matplotlib/tests/test_polar.py b/lib/matplotlib/tests/test_polar.py index 9f50c6890400..768d66873e58 100644 --- a/lib/matplotlib/tests/test_polar.py +++ b/lib/matplotlib/tests/test_polar.py @@ -1,5 +1,3 @@ -import platform - import numpy as np from numpy.testing import assert_allclose import pytest @@ -9,8 +7,7 @@ from matplotlib.testing.decorators import image_comparison, check_figures_equal -@image_comparison(['polar_axes'], style='default', - tol=0 if platform.machine() == 'x86_64' else 0.01) +@image_comparison(['polar_axes'], style='default', tol=0.012) def test_polar_annotations(): # You can specify the xypoint and the xytext in different positions and # coordinate systems, and optionally turn on a connecting line and mark the @@ -44,7 +41,8 @@ def test_polar_annotations(): ax.tick_params(axis='x', tick1On=True, tick2On=True, direction='out') -@image_comparison(['polar_coords'], style='default', remove_text=True) +@image_comparison(['polar_coords'], style='default', remove_text=True, + tol=0.012) def test_polar_coord_annotations(): # You can also use polar notation on a cartesian axes. Here the native # coordinate system ('data') is cartesian, so you need to specify the diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 0007a3babc06..dc7824f3204d 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1428,6 +1428,8 @@ def _get_xy_transform(self, renderer, s): bbox_name, unit = s_ # if unit is offset-like if bbox_name == "figure": + bbox0 = self.figure.figbbox + elif bbox_name == "subfigure": bbox0 = self.figure.bbox elif bbox_name == "axes": bbox0 = self.axes.bbox @@ -1611,19 +1613,27 @@ def __init__(self, text, xy, - One of the following strings: - ================= ============================================= - Value Description - ================= ============================================= - 'figure points' Points from the lower left of the figure - 'figure pixels' Pixels from the lower left of the figure - 'figure fraction' Fraction of figure from lower left - 'axes points' Points from lower left corner of axes - 'axes pixels' Pixels from lower left corner of axes - 'axes fraction' Fraction of axes from lower left - 'data' Use the coordinate system of the object being - annotated (default) - 'polar' *(theta, r)* if not native 'data' coordinates - ================= ============================================= + ==================== ============================================ + Value Description + ==================== ============================================ + 'figure points' Points from the lower left of the figure + 'figure pixels' Pixels from the lower left of the figure + 'figure fraction' Fraction of figure from lower left + 'subfigure points' Points from the lower left of the subfigure + 'subfigure pixels' Pixels from the lower left of the subfigure + 'subfigure fraction' Fraction of subfigure from lower left + 'axes points' Points from lower left corner of axes + 'axes pixels' Pixels from lower left corner of axes + 'axes fraction' Fraction of axes from lower left + 'data' Use the coordinate system of the object + being annotated (default) + 'polar' *(theta, r)* if not native 'data' + coordinates + ==================== ============================================ + + Note that 'subfigure pixels' and 'figure pixels' are the same + for the parent figure, so users who want code that is usable in + a subfigure can use 'subfigure pixels'. - An `.Artist`: *xy* is interpreted as a fraction of the artist's `~matplotlib.transforms.Bbox`. E.g. *(0, 0)* would be the lower diff --git a/lib/mpl_toolkits/axes_grid1/colorbar.py b/lib/mpl_toolkits/axes_grid1/colorbar.py index 97cf11c45f51..77af5a1ab7fc 100644 --- a/lib/mpl_toolkits/axes_grid1/colorbar.py +++ b/lib/mpl_toolkits/axes_grid1/colorbar.py @@ -45,8 +45,8 @@ *fraction* 0.15; fraction of original axes to use for colorbar *pad* Defaults to 0.05 if vertical, 0.15 if horizontal; fraction of original axes between colorbar and new image axes. - Defaults to 0.05 for both if `.get_constrained_layout` - is *True*. + Defaults to 0.05 for both if + `.Figure.get_constrained_layout` is *True*. *shrink* 1.0; fraction by which to shrink the colorbar *aspect* 20; ratio of long to short dimensions ============= ==================================================== diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 92c4f2ba927e..32243f5659ab 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -377,8 +377,11 @@ def apply_aspect(self, position=None): # in the superclass, we would go through and actually deal with axis # scales and box/datalim. Those are all irrelevant - all we need to do # is make sure our coordinate system is square. - figW, figH = self.get_figure().get_size_inches() - fig_aspect = figH / figW + trans = self.get_figure().transSubfigure + bb = mtransforms.Bbox.from_bounds(0, 0, 1, 1).transformed(trans) + # this is the physical aspect of the panel (or figure): + fig_aspect = bb.height / bb.width + box_aspect = 1 pb = position.frozen() pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index 5ac5b1e62e4d..e638614d4185 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -371,7 +371,7 @@ def test_marker_draw_order_view_rotated(fig_test, fig_ref): ax.view_init(elev=0, azim=azim - 180) # view rotated by 180 degrees -@mpl3d_image_comparison(['plot_3d_from_2d.png'], tol=0.01) +@mpl3d_image_comparison(['plot_3d_from_2d.png'], tol=0.015) def test_plot_3d_from_2d(): fig = plt.figure() ax = fig.add_subplot(projection='3d') diff --git a/tutorials/advanced/transforms_tutorial.py b/tutorials/advanced/transforms_tutorial.py index 09bedc428814..3fe230f3fd6a 100644 --- a/tutorials/advanced/transforms_tutorial.py +++ b/tutorials/advanced/transforms_tutorial.py @@ -29,6 +29,13 @@ | | |is bottom left of the axes, and | | | |(1, 1) is top right of the axes. | +----------------+-----------------------------+-----------------------------------+ +|"subfigure" |``subfigure.transSubfigure`` |The coordinate system of the | +| | |`.SubFigure`; (0, 0) is bottom left| +| | |of the subfigure, and (1, 1) is top| +| | |right of the subfigure. If a | +| | |figure has no subfigures, this is | +| | |the same as ``transFigure``. | ++----------------+-----------------------------+-----------------------------------+ |"figure" |``fig.transFigure`` |The coordinate system of the | | | |`.Figure`; (0, 0) is bottom left | | | |of the figure, and (1, 1) is top |