1
- from collections .abc import MutableSequence
1
+ from collections .abc import Iterable , MutableSequence
2
2
from contextlib import ExitStack
3
3
import functools
4
4
import inspect
18
18
import matplotlib .collections as mcoll
19
19
import matplotlib .colors as mcolors
20
20
import matplotlib .font_manager as font_manager
21
+ from matplotlib .gridspec import SubplotSpec
21
22
import matplotlib .image as mimage
22
23
import matplotlib .lines as mlines
23
24
import matplotlib .patches as mpatches
@@ -569,8 +570,8 @@ def __str__(self):
569
570
return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})" .format (
570
571
type (self ).__name__ , self ._position .bounds )
571
572
572
- def __init__ (self , fig , rect ,
573
- * ,
573
+ def __init__ (self , fig ,
574
+ * args ,
574
575
facecolor = None , # defaults to rc axes.facecolor
575
576
frameon = True ,
576
577
sharex = None , # use Axes instance's xaxis info
@@ -589,9 +590,18 @@ def __init__(self, fig, rect,
589
590
fig : `~matplotlib.figure.Figure`
590
591
The Axes is built in the `.Figure` *fig*.
591
592
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.
595
605
596
606
sharex, sharey : `~.axes.Axes`, optional
597
607
The x or y `~.matplotlib.axis` is shared with the x or
@@ -616,10 +626,21 @@ def __init__(self, fig, rect,
616
626
"""
617
627
618
628
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 ])
621
641
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 )
623
644
if self ._position .width < 0 or self ._position .height < 0 :
624
645
raise ValueError ('Width and height specified must be non-negative' )
625
646
self ._originalPosition = self ._position .frozen ()
@@ -632,8 +653,14 @@ def __init__(self, fig, rect,
632
653
self ._sharey = sharey
633
654
self .set_label (label )
634
655
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 )
635
661
self .set_box_aspect (box_aspect )
636
662
self ._axes_locator = None # Optionally set via update(kwargs).
663
+
637
664
# placeholder for any colorbars added that use this Axes.
638
665
# (see colorbar.py):
639
666
self ._colorbars = []
@@ -737,6 +764,19 @@ def __repr__(self):
737
764
fields += [f"{ name } label={ axis .get_label ().get_text ()!r} " ]
738
765
return f"<{ self .__class__ .__name__ } : " + ", " .join (fields ) + ">"
739
766
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
+
740
780
@_api .delete_parameter ("3.6" , "args" )
741
781
@_api .delete_parameter ("3.6" , "kwargs" )
742
782
def get_window_extent (self , renderer = None , * args , ** kwargs ):
@@ -4424,17 +4464,23 @@ def get_tightbbox(self, renderer=None, call_axes_locator=True,
4424
4464
4425
4465
def _make_twin_axes (self , * args , ** kwargs ):
4426
4466
"""Make a twinx Axes of self. This is used for twinx and twiny."""
4427
- # Typically, SubplotBase._make_twin_axes is called instead of this.
4428
4467
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 ))
4434
4480
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
4438
4484
4439
4485
def twinx (self ):
4440
4486
"""
@@ -4502,3 +4548,56 @@ def get_shared_x_axes(self):
4502
4548
def get_shared_y_axes (self ):
4503
4549
"""Return an immutable view on the shared y-axes Grouper."""
4504
4550
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