1- from collections .abc import MutableSequence
1+ from collections .abc import Iterable , MutableSequence
22from contextlib import ExitStack
33import functools
44import inspect
1818import matplotlib .collections as mcoll
1919import matplotlib .colors as mcolors
2020import matplotlib .font_manager as font_manager
21+ from matplotlib .gridspec import SubplotSpec
2122import matplotlib .image as mimage
2223import matplotlib .lines as mlines
2324import matplotlib .patches as mpatches
@@ -569,8 +570,8 @@ def __str__(self):
569570 return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})" .format (
570571 type (self ).__name__ , self ._position .bounds )
571572
572- def __init__ (self , fig , rect ,
573- * ,
573+ def __init__ (self , fig ,
574+ * args ,
574575 facecolor = None , # defaults to rc axes.facecolor
575576 frameon = True ,
576577 sharex = None , # use Axes instance's xaxis info
@@ -589,9 +590,18 @@ def __init__(self, fig, rect,
589590 fig : `~matplotlib.figure.Figure`
590591 The Axes is built in the `.Figure` *fig*.
591592
592- rect : tuple (left, bottom, width, height).
593- The Axes is built in the rectangle *rect*. *rect* is in
594- `.Figure` coordinates.
593+ *args
594+ ``*args`` can be a single ``(left, bottom, width, height)``
595+ rectangle or a single `.Bbox`. This specifies the rectangle (in
596+ figure coordinates) where the Axes is positioned.
597+
598+ ``*args`` can also consist of three numbers or a single three-digit
599+ number; in the latter case, the digits are considered as
600+ independent numbers. The numbers are interpreted as ``(nrows,
601+ ncols, index)``: ``(nrows, ncols)`` specifies the size of an array
602+ of subplots, and ``index`` is the 1-based index of the subplot
603+ being created. Finally, ``*args`` can also directly be a
604+ `.SubplotSpec` instance.
595605
596606 sharex, sharey : `~.axes.Axes`, optional
597607 The x or y `~.matplotlib.axis` is shared with the x or
@@ -616,10 +626,21 @@ def __init__(self, fig, rect,
616626 """
617627
618628 super ().__init__ ()
619- if isinstance (rect , mtransforms .Bbox ):
620- self ._position = rect
629+ if "rect" in kwargs :
630+ if args :
631+ raise TypeError (
632+ "'rect' cannot be used together with positional arguments" )
633+ rect = kwargs .pop ("rect" )
634+ _api .check_isinstance ((mtransforms .Bbox , Iterable ), rect = rect )
635+ args = (rect ,)
636+ subplotspec = None
637+ if len (args ) == 1 and isinstance (args [0 ], mtransforms .Bbox ):
638+ self ._position = args [0 ]
639+ elif len (args ) == 1 and np .iterable (args [0 ]):
640+ self ._position = mtransforms .Bbox .from_bounds (* args [0 ])
621641 else :
622- self ._position = mtransforms .Bbox .from_bounds (* rect )
642+ self ._position = self ._originalPosition = mtransforms .Bbox .unit ()
643+ subplotspec = SubplotSpec ._from_subplot_args (fig , args )
623644 if self ._position .width < 0 or self ._position .height < 0 :
624645 raise ValueError ('Width and height specified must be non-negative' )
625646 self ._originalPosition = self ._position .frozen ()
@@ -632,8 +653,16 @@ def __init__(self, fig, rect,
632653 self ._sharey = sharey
633654 self .set_label (label )
634655 self .set_figure (fig )
656+ # The subplotspec needs to be set after the figure (so that
657+ # figure-level subplotpars are taken into account), but the figure
658+ # needs to be set after self._position is initialized.
659+ if subplotspec :
660+ self .set_subplotspec (subplotspec )
661+ else :
662+ self ._subplotspec = None
635663 self .set_box_aspect (box_aspect )
636664 self ._axes_locator = None # Optionally set via update(kwargs).
665+
637666 # placeholder for any colorbars added that use this Axes.
638667 # (see colorbar.py):
639668 self ._colorbars = []
@@ -737,6 +766,19 @@ def __repr__(self):
737766 fields += [f"{ name } label={ axis .get_label ().get_text ()!r} " ]
738767 return f"<{ self .__class__ .__name__ } : " + ", " .join (fields ) + ">"
739768
769+ def get_subplotspec (self ):
770+ """Return the `.SubplotSpec` associated with the subplot, or None."""
771+ return self ._subplotspec
772+
773+ def set_subplotspec (self , subplotspec ):
774+ """Set the `.SubplotSpec`. associated with the subplot."""
775+ self ._subplotspec = subplotspec
776+ self ._set_position (subplotspec .get_position (self .figure ))
777+
778+ def get_gridspec (self ):
779+ """Return the `.GridSpec` associated with the subplot, or None."""
780+ return self ._subplotspec .get_gridspec () if self ._subplotspec else None
781+
740782 @_api .delete_parameter ("3.6" , "args" )
741783 @_api .delete_parameter ("3.6" , "kwargs" )
742784 def get_window_extent (self , renderer = None , * args , ** kwargs ):
@@ -4424,17 +4466,23 @@ def get_tightbbox(self, renderer=None, call_axes_locator=True,
44244466
44254467 def _make_twin_axes (self , * args , ** kwargs ):
44264468 """Make a twinx Axes of self. This is used for twinx and twiny."""
4427- # Typically, SubplotBase._make_twin_axes is called instead of this.
44284469 if 'sharex' in kwargs and 'sharey' in kwargs :
4429- raise ValueError ("Twinned Axes may share only one axis" )
4430- ax2 = self .figure .add_axes (
4431- self .get_position (True ), * args , ** kwargs ,
4432- axes_locator = _TransformedBoundsLocator (
4433- [0 , 0 , 1 , 1 ], self .transAxes ))
4470+ # The following line is added in v2.2 to avoid breaking Seaborn,
4471+ # which currently uses this internal API.
4472+ if kwargs ["sharex" ] is not self and kwargs ["sharey" ] is not self :
4473+ raise ValueError ("Twinned Axes may share only one axis" )
4474+ ss = self .get_subplotspec ()
4475+ if ss :
4476+ twin = self .figure .add_subplot (ss , * args , ** kwargs )
4477+ else :
4478+ twin = self .figure .add_axes (
4479+ self .get_position (True ), * args , ** kwargs ,
4480+ axes_locator = _TransformedBoundsLocator (
4481+ [0 , 0 , 1 , 1 ], self .transAxes ))
44344482 self .set_adjustable ('datalim' )
4435- ax2 .set_adjustable ('datalim' )
4436- self ._twinned_axes .join (self , ax2 )
4437- return ax2
4483+ twin .set_adjustable ('datalim' )
4484+ self ._twinned_axes .join (self , twin )
4485+ return twin
44384486
44394487 def twinx (self ):
44404488 """
@@ -4502,3 +4550,56 @@ def get_shared_x_axes(self):
45024550 def get_shared_y_axes (self ):
45034551 """Return an immutable view on the shared y-axes Grouper."""
45044552 return cbook .GrouperView (self ._shared_axes ["y" ])
4553+
4554+ def label_outer (self ):
4555+ """
4556+ Only show "outer" labels and tick labels.
4557+
4558+ x-labels are only kept for subplots on the last row (or first row, if
4559+ labels are on the top side); y-labels only for subplots on the first
4560+ column (or last column, if labels are on the right side).
4561+ """
4562+ self ._label_outer_xaxis (check_patch = False )
4563+ self ._label_outer_yaxis (check_patch = False )
4564+
4565+ def _label_outer_xaxis (self , * , check_patch ):
4566+ # see documentation in label_outer.
4567+ if check_patch and not isinstance (self .patch , mpl .patches .Rectangle ):
4568+ return
4569+ ss = self .get_subplotspec ()
4570+ if not ss :
4571+ return
4572+ label_position = self .xaxis .get_label_position ()
4573+ if not ss .is_first_row (): # Remove top label/ticklabels/offsettext.
4574+ if label_position == "top" :
4575+ self .set_xlabel ("" )
4576+ self .xaxis .set_tick_params (which = "both" , labeltop = False )
4577+ if self .xaxis .offsetText .get_position ()[1 ] == 1 :
4578+ self .xaxis .offsetText .set_visible (False )
4579+ if not ss .is_last_row (): # Remove bottom label/ticklabels/offsettext.
4580+ if label_position == "bottom" :
4581+ self .set_xlabel ("" )
4582+ self .xaxis .set_tick_params (which = "both" , labelbottom = False )
4583+ if self .xaxis .offsetText .get_position ()[1 ] == 0 :
4584+ self .xaxis .offsetText .set_visible (False )
4585+
4586+ def _label_outer_yaxis (self , * , check_patch ):
4587+ # see documentation in label_outer.
4588+ if check_patch and not isinstance (self .patch , mpl .patches .Rectangle ):
4589+ return
4590+ ss = self .get_subplotspec ()
4591+ if not ss :
4592+ return
4593+ label_position = self .yaxis .get_label_position ()
4594+ if not ss .is_first_col (): # Remove left label/ticklabels/offsettext.
4595+ if label_position == "left" :
4596+ self .set_ylabel ("" )
4597+ self .yaxis .set_tick_params (which = "both" , labelleft = False )
4598+ if self .yaxis .offsetText .get_position ()[0 ] == 0 :
4599+ self .yaxis .offsetText .set_visible (False )
4600+ if not ss .is_last_col (): # Remove right label/ticklabels/offsettext.
4601+ if label_position == "right" :
4602+ self .set_ylabel ("" )
4603+ self .yaxis .set_tick_params (which = "both" , labelright = False )
4604+ if self .yaxis .offsetText .get_position ()[0 ] == 1 :
4605+ self .yaxis .offsetText .set_visible (False )
0 commit comments