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
@@ -571,8 +572,8 @@ def __str__(self):
571572 return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})" .format (
572573 type (self ).__name__ , self ._position .bounds )
573574
574- def __init__ (self , fig , rect ,
575- * ,
575+ def __init__ (self , fig ,
576+ * args ,
576577 facecolor = None , # defaults to rc axes.facecolor
577578 frameon = True ,
578579 sharex = None , # use Axes instance's xaxis info
@@ -591,9 +592,18 @@ def __init__(self, fig, rect,
591592 fig : `~matplotlib.figure.Figure`
592593 The Axes is built in the `.Figure` *fig*.
593594
594- rect : tuple (left, bottom, width, height).
595- The Axes is built in the rectangle *rect*. *rect* is in
596- `.Figure` coordinates.
595+ *args
596+ ``*args`` can be a single ``(left, bottom, width, height)``
597+ rectangle or a single `.Bbox`. This specifies the rectangle (in
598+ figure coordinates) where the Axes is positioned.
599+
600+ ``*args`` can also consist of three numbers or a single three-digit
601+ number; in the latter case, the digits are considered as
602+ independent numbers. The numbers are interpreted as ``(nrows,
603+ ncols, index)``: ``(nrows, ncols)`` specifies the size of an array
604+ of subplots, and ``index`` is the 1-based index of the subplot
605+ being created. Finally, ``*args`` can also directly be a
606+ `.SubplotSpec` instance.
597607
598608 sharex, sharey : `~.axes.Axes`, optional
599609 The x or y `~.matplotlib.axis` is shared with the x or
@@ -618,10 +628,21 @@ def __init__(self, fig, rect,
618628 """
619629
620630 super ().__init__ ()
621- if isinstance (rect , mtransforms .Bbox ):
622- self ._position = rect
631+ if "rect" in kwargs :
632+ if args :
633+ raise TypeError (
634+ "'rect' cannot be used together with positional arguments" )
635+ rect = kwargs .pop ("rect" )
636+ _api .check_isinstance ((mtransforms .Bbox , Iterable ), rect = rect )
637+ args = (rect ,)
638+ subplotspec = None
639+ if len (args ) == 1 and isinstance (args [0 ], mtransforms .Bbox ):
640+ self ._position = args [0 ]
641+ elif len (args ) == 1 and np .iterable (args [0 ]):
642+ self ._position = mtransforms .Bbox .from_bounds (* args [0 ])
623643 else :
624- self ._position = mtransforms .Bbox .from_bounds (* rect )
644+ self ._position = self ._originalPosition = mtransforms .Bbox .unit ()
645+ subplotspec = SubplotSpec ._from_subplot_args (fig , args )
625646 if self ._position .width < 0 or self ._position .height < 0 :
626647 raise ValueError ('Width and height specified must be non-negative' )
627648 self ._originalPosition = self ._position .frozen ()
@@ -634,8 +655,16 @@ def __init__(self, fig, rect,
634655 self ._sharey = sharey
635656 self .set_label (label )
636657 self .set_figure (fig )
658+ # The subplotspec needs to be set after the figure (so that
659+ # figure-level subplotpars are taken into account), but the figure
660+ # needs to be set after self._position is initialized.
661+ if subplotspec :
662+ self .set_subplotspec (subplotspec )
663+ else :
664+ self ._subplotspec = None
637665 self .set_box_aspect (box_aspect )
638666 self ._axes_locator = None # Optionally set via update(kwargs).
667+
639668 # placeholder for any colorbars added that use this Axes.
640669 # (see colorbar.py):
641670 self ._colorbars = []
@@ -753,6 +782,19 @@ def __repr__(self):
753782 fields += [f"{ name } label={ axis .get_label ().get_text ()!r} " ]
754783 return f"<{ self .__class__ .__name__ } : " + ", " .join (fields ) + ">"
755784
785+ def get_subplotspec (self ):
786+ """Return the `.SubplotSpec` associated with the subplot, or None."""
787+ return self ._subplotspec
788+
789+ def set_subplotspec (self , subplotspec ):
790+ """Set the `.SubplotSpec`. associated with the subplot."""
791+ self ._subplotspec = subplotspec
792+ self ._set_position (subplotspec .get_position (self .figure ))
793+
794+ def get_gridspec (self ):
795+ """Return the `.GridSpec` associated with the subplot, or None."""
796+ return self ._subplotspec .get_gridspec () if self ._subplotspec else None
797+
756798 @_api .delete_parameter ("3.6" , "args" )
757799 @_api .delete_parameter ("3.6" , "kwargs" )
758800 def get_window_extent (self , renderer = None , * args , ** kwargs ):
@@ -4460,17 +4502,23 @@ def get_tightbbox(self, renderer=None, call_axes_locator=True,
44604502
44614503 def _make_twin_axes (self , * args , ** kwargs ):
44624504 """Make a twinx Axes of self. This is used for twinx and twiny."""
4463- # Typically, SubplotBase._make_twin_axes is called instead of this.
44644505 if 'sharex' in kwargs and 'sharey' in kwargs :
4465- raise ValueError ("Twinned Axes may share only one axis" )
4466- ax2 = self .figure .add_axes (
4467- self .get_position (True ), * args , ** kwargs ,
4468- axes_locator = _TransformedBoundsLocator (
4469- [0 , 0 , 1 , 1 ], self .transAxes ))
4506+ # The following line is added in v2.2 to avoid breaking Seaborn,
4507+ # which currently uses this internal API.
4508+ if kwargs ["sharex" ] is not self and kwargs ["sharey" ] is not self :
4509+ raise ValueError ("Twinned Axes may share only one axis" )
4510+ ss = self .get_subplotspec ()
4511+ if ss :
4512+ twin = self .figure .add_subplot (ss , * args , ** kwargs )
4513+ else :
4514+ twin = self .figure .add_axes (
4515+ self .get_position (True ), * args , ** kwargs ,
4516+ axes_locator = _TransformedBoundsLocator (
4517+ [0 , 0 , 1 , 1 ], self .transAxes ))
44704518 self .set_adjustable ('datalim' )
4471- ax2 .set_adjustable ('datalim' )
4472- self ._twinned_axes .join (self , ax2 )
4473- return ax2
4519+ twin .set_adjustable ('datalim' )
4520+ self ._twinned_axes .join (self , twin )
4521+ return twin
44744522
44754523 def twinx (self ):
44764524 """
@@ -4538,3 +4586,56 @@ def get_shared_x_axes(self):
45384586 def get_shared_y_axes (self ):
45394587 """Return an immutable view on the shared y-axes Grouper."""
45404588 return cbook .GrouperView (self ._shared_axes ["y" ])
4589+
4590+ def label_outer (self ):
4591+ """
4592+ Only show "outer" labels and tick labels.
4593+
4594+ x-labels are only kept for subplots on the last row (or first row, if
4595+ labels are on the top side); y-labels only for subplots on the first
4596+ column (or last column, if labels are on the right side).
4597+ """
4598+ self ._label_outer_xaxis (check_patch = False )
4599+ self ._label_outer_yaxis (check_patch = False )
4600+
4601+ def _label_outer_xaxis (self , * , check_patch ):
4602+ # see documentation in label_outer.
4603+ if check_patch and not isinstance (self .patch , mpl .patches .Rectangle ):
4604+ return
4605+ ss = self .get_subplotspec ()
4606+ if not ss :
4607+ return
4608+ label_position = self .xaxis .get_label_position ()
4609+ if not ss .is_first_row (): # Remove top label/ticklabels/offsettext.
4610+ if label_position == "top" :
4611+ self .set_xlabel ("" )
4612+ self .xaxis .set_tick_params (which = "both" , labeltop = False )
4613+ if self .xaxis .offsetText .get_position ()[1 ] == 1 :
4614+ self .xaxis .offsetText .set_visible (False )
4615+ if not ss .is_last_row (): # Remove bottom label/ticklabels/offsettext.
4616+ if label_position == "bottom" :
4617+ self .set_xlabel ("" )
4618+ self .xaxis .set_tick_params (which = "both" , labelbottom = False )
4619+ if self .xaxis .offsetText .get_position ()[1 ] == 0 :
4620+ self .xaxis .offsetText .set_visible (False )
4621+
4622+ def _label_outer_yaxis (self , * , check_patch ):
4623+ # see documentation in label_outer.
4624+ if check_patch and not isinstance (self .patch , mpl .patches .Rectangle ):
4625+ return
4626+ ss = self .get_subplotspec ()
4627+ if not ss :
4628+ return
4629+ label_position = self .yaxis .get_label_position ()
4630+ if not ss .is_first_col (): # Remove left label/ticklabels/offsettext.
4631+ if label_position == "left" :
4632+ self .set_ylabel ("" )
4633+ self .yaxis .set_tick_params (which = "both" , labelleft = False )
4634+ if self .yaxis .offsetText .get_position ()[0 ] == 0 :
4635+ self .yaxis .offsetText .set_visible (False )
4636+ if not ss .is_last_col (): # Remove right label/ticklabels/offsettext.
4637+ if label_position == "right" :
4638+ self .set_ylabel ("" )
4639+ self .yaxis .set_tick_params (which = "both" , labelright = False )
4640+ if self .yaxis .offsetText .get_position ()[0 ] == 1 :
4641+ self .yaxis .offsetText .set_visible (False )
0 commit comments