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+ self ._subplotspec = 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,14 @@ 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 )
635661 self .set_box_aspect (box_aspect )
636662 self ._axes_locator = None # Optionally set via update(kwargs).
663+
637664 # placeholder for any colorbars added that use this Axes.
638665 # (see colorbar.py):
639666 self ._colorbars = []
@@ -737,6 +764,19 @@ def __repr__(self):
737764 fields += [f"{ name } label={ axis .get_label ().get_text ()!r} " ]
738765 return f"<{ self .__class__ .__name__ } : " + ", " .join (fields ) + ">"
739766
767+ def get_subplotspec (self ):
768+ """Return the `.SubplotSpec` associated with the subplot, or None."""
769+ return self ._subplotspec
770+
771+ def set_subplotspec (self , subplotspec ):
772+ """Set the `.SubplotSpec`. associated with the subplot."""
773+ self ._subplotspec = subplotspec
774+ self ._set_position (subplotspec .get_position (self .figure ))
775+
776+ def get_gridspec (self ):
777+ """Return the `.GridSpec` associated with the subplot, or None."""
778+ return self ._subplotspec .get_gridspec () if self ._subplotspec else None
779+
740780 @_api .delete_parameter ("3.6" , "args" )
741781 @_api .delete_parameter ("3.6" , "kwargs" )
742782 def get_window_extent (self , renderer = None , * args , ** kwargs ):
@@ -4424,17 +4464,23 @@ def get_tightbbox(self, renderer=None, call_axes_locator=True,
44244464
44254465 def _make_twin_axes (self , * args , ** kwargs ):
44264466 """Make a twinx Axes of self. This is used for twinx and twiny."""
4427- # Typically, SubplotBase._make_twin_axes is called instead of this.
44284467 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 ))
4468+ # The following line is added in v2.2 to avoid breaking Seaborn,
4469+ # which currently uses this internal API.
4470+ if kwargs ["sharex" ] is not self and kwargs ["sharey" ] is not self :
4471+ raise ValueError ("Twinned Axes may share only one axis" )
4472+ ss = self .get_subplotspec ()
4473+ if ss :
4474+ twin = self .figure .add_subplot (ss , * args , ** kwargs )
4475+ else :
4476+ twin = self .figure .add_axes (
4477+ self .get_position (True ), * args , ** kwargs ,
4478+ axes_locator = _TransformedBoundsLocator (
4479+ [0 , 0 , 1 , 1 ], self .transAxes ))
44344480 self .set_adjustable ('datalim' )
4435- ax2 .set_adjustable ('datalim' )
4436- self ._twinned_axes .join (self , ax2 )
4437- return ax2
4481+ twin .set_adjustable ('datalim' )
4482+ self ._twinned_axes .join (self , twin )
4483+ return twin
44384484
44394485 def twinx (self ):
44404486 """
@@ -4502,3 +4548,56 @@ def get_shared_x_axes(self):
45024548 def get_shared_y_axes (self ):
45034549 """Return an immutable view on the shared y-axes Grouper."""
45044550 return cbook .GrouperView (self ._shared_axes ["y" ])
4551+
4552+ def label_outer (self ):
4553+ """
4554+ Only show "outer" labels and tick labels.
4555+
4556+ x-labels are only kept for subplots on the last row (or first row, if
4557+ labels are on the top side); y-labels only for subplots on the first
4558+ column (or last column, if labels are on the right side).
4559+ """
4560+ self ._label_outer_xaxis (check_patch = False )
4561+ self ._label_outer_yaxis (check_patch = False )
4562+
4563+ def _label_outer_xaxis (self , * , check_patch ):
4564+ # see documentation in label_outer.
4565+ if check_patch and not isinstance (self .patch , mpl .patches .Rectangle ):
4566+ return
4567+ ss = self .get_subplotspec ()
4568+ if not ss :
4569+ return
4570+ label_position = self .xaxis .get_label_position ()
4571+ if not ss .is_first_row (): # Remove top label/ticklabels/offsettext.
4572+ if label_position == "top" :
4573+ self .set_xlabel ("" )
4574+ self .xaxis .set_tick_params (which = "both" , labeltop = False )
4575+ if self .xaxis .offsetText .get_position ()[1 ] == 1 :
4576+ self .xaxis .offsetText .set_visible (False )
4577+ if not ss .is_last_row (): # Remove bottom label/ticklabels/offsettext.
4578+ if label_position == "bottom" :
4579+ self .set_xlabel ("" )
4580+ self .xaxis .set_tick_params (which = "both" , labelbottom = False )
4581+ if self .xaxis .offsetText .get_position ()[1 ] == 0 :
4582+ self .xaxis .offsetText .set_visible (False )
4583+
4584+ def _label_outer_yaxis (self , * , check_patch ):
4585+ # see documentation in label_outer.
4586+ if check_patch and not isinstance (self .patch , mpl .patches .Rectangle ):
4587+ return
4588+ ss = self .get_subplotspec ()
4589+ if not ss :
4590+ return
4591+ label_position = self .yaxis .get_label_position ()
4592+ if not ss .is_first_col (): # Remove left label/ticklabels/offsettext.
4593+ if label_position == "left" :
4594+ self .set_ylabel ("" )
4595+ self .yaxis .set_tick_params (which = "both" , labelleft = False )
4596+ if self .yaxis .offsetText .get_position ()[0 ] == 0 :
4597+ self .yaxis .offsetText .set_visible (False )
4598+ if not ss .is_last_col (): # Remove right label/ticklabels/offsettext.
4599+ if label_position == "right" :
4600+ self .set_ylabel ("" )
4601+ self .yaxis .set_tick_params (which = "both" , labelright = False )
4602+ if self .yaxis .offsetText .get_position ()[0 ] == 1 :
4603+ self .yaxis .offsetText .set_visible (False )
0 commit comments