2525
2626import numpy as np
2727
28- from matplotlib import cbook
2928from matplotlib import rcParams
3029from matplotlib import cbook , docstring
3130from matplotlib .artist import Artist , allow_rasterization
32- from matplotlib .cbook import silent_list , is_hashable , warn_deprecated
31+ from matplotlib .cbook import (silent_list , is_hashable , warn_deprecated ,
32+ _valid_compass , _map_loc_to_compass )
3333from matplotlib .font_manager import FontProperties
3434from matplotlib .lines import Line2D
3535from matplotlib .patches import Patch , Rectangle , Shadow , FancyBboxPatch
@@ -115,13 +115,14 @@ def _update_bbox_to_anchor(self, loc_in_canvas):
115115 The location of the legend.
116116
117117 The strings
118- ``'upper left', 'upper right', 'lower left', 'lower right'``
119- place the legend at the corresponding corner of the axes/figure.
118+ ``'upper left', 'upper right', 'lower left', 'lower right'`` and their
119+ corresponding compass strings (see table below) place the legend at the
120+ respective corner of the axes/figure.
120121
121122 The strings
122- ``'upper center', 'lower center', 'center left', 'center right'``
123- place the legend at the center of the corresponding edge of the
124- axes/figure.
123+ ``'upper center', 'lower center', 'center left', 'center right'`` and their
124+ corresponding compass strings place the legend at the center of the
125+ respective edge of the axes/figure.
125126
126127 The string ``'center'`` places the legend at the center of the axes/figure.
127128
@@ -136,23 +137,28 @@ def _update_bbox_to_anchor(self, loc_in_canvas):
136137
137138 For back-compatibility, ``'center right'`` (but no other location) can also
138139 be spelled ``'right'``, and each "string" locations can also be given as a
139- numeric value:
140-
141- =============== =============
142- Location String Location Code
143- =============== =============
144- 'best' 0
145- 'upper right' 1
146- 'upper left' 2
147- 'lower left' 3
148- 'lower right' 4
149- 'right' 5
150- 'center left' 6
151- 'center right' 7
152- 'lower center' 8
153- 'upper center' 9
154- 'center' 10
155- =============== =============
140+ numeric value. Possible (case-sensitive) strings and codes are:
141+
142+ ============ ============== =============== =============
143+ Compass Code Compass String Location String Location Code
144+ ============ ============== =============== =============
145+ .. 'best' 0
146+ 'NE' 'northeast' 'upper right' 1
147+ 'NW' 'northwest' 'upper left' 2
148+ 'SW' 'southwest' 'lower left' 3
149+ 'SE' 'southeast' 'lower right' 4
150+ .. 'right' 5
151+ 'W' 'west' 'center left' 6
152+ 'E' 'east' 'center right' 7
153+ 'S' 'south' 'lower center' 8
154+ 'N' 'north' 'upper center' 9
155+ 'C' 'center' 'center' 10
156+ ============ ============== =============== =============
157+
158+
159+ Alternatively can be a 2-tuple giving ``x, y`` of the lower-left
160+ corner of the legend in axes coordinates (in which case
161+ ``bbox_to_anchor`` will be ignored).
156162
157163
158164bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats
@@ -324,21 +330,7 @@ class Legend(Artist):
324330 Place a legend on the axes at location loc.
325331
326332 """
327- codes = {'best' : 0 , # only implemented for axes legends
328- 'upper right' : 1 ,
329- 'upper left' : 2 ,
330- 'lower left' : 3 ,
331- 'lower right' : 4 ,
332- 'right' : 5 ,
333- 'center left' : 6 ,
334- 'center right' : 7 ,
335- 'lower center' : 8 ,
336- 'upper center' : 9 ,
337- 'center' : 10 ,
338- }
339- compasscodes = {'nw' : 2 , 'n' : 9 , 'ne' : 1 , 'w' : 6 , 'c' : 10 , 'e' : 7 ,
340- 'sw' : 3 , 's' : 8 , 'se' : 4 }
341- allcodes = {** codes , ** compasscodes }
333+
342334 zorder = 5
343335
344336 def __str__ (self ):
@@ -502,36 +494,6 @@ def __init__(self, parent, handles, labels,
502494 raise TypeError ("Legend needs either Axes or Figure as parent" )
503495 self .parent = parent
504496
505- self ._loc_used_default = loc is None
506- if loc is None :
507- loc = rcParams ["legend.loc" ]
508- if not self .isaxes and loc in [0 , 'best' ]:
509- loc = 'upper right'
510- if isinstance (loc , str ):
511- if loc .lower () not in self .allcodes :
512- if self .isaxes :
513- cbook .warn_deprecated (
514- "3.1" , message = "Unrecognized location {!r}. Falling "
515- "back on 'best'; valid locations are\n \t {}\n "
516- "This will raise an exception %(removal)s."
517- .format (loc , '\n \t ' .join (self .allcodes )))
518- loc = 0
519- else :
520- cbook .warn_deprecated (
521- "3.1" , message = "Unrecognized location {!r}. Falling "
522- "back on 'upper right'; valid locations are\n \t {}\n '"
523- "This will raise an exception %(removal)s."
524- .format (loc , '\n \t ' .join (self .allcodes )))
525- loc = 1
526- else :
527- loc = self .allcodes [loc .lower ()]
528- if not self .isaxes and loc == 0 :
529- cbook .warn_deprecated (
530- "3.1" , message = "Automatic legend placement (loc='best') not "
531- "implemented for figure legend. Falling back on 'upper "
532- "right'. This will raise an exception %(removal)s." )
533- loc = 1
534-
535497 self ._mode = mode
536498 self .set_bbox_to_anchor (bbox_to_anchor , bbox_transform )
537499
@@ -577,6 +539,10 @@ def __init__(self, parent, handles, labels,
577539 # init with null renderer
578540 self ._init_legend_box (handles , labels , markerfirst )
579541
542+ # location must be set after _legend_box is created.
543+ self ._loc_used_default = loc is None
544+ self ._loc = loc
545+
580546 # If shadow is activated use framealpha if not
581547 # explicitly passed. See Issue 8943
582548 if framealpha is None :
@@ -587,10 +553,6 @@ def __init__(self, parent, handles, labels,
587553 else :
588554 self .get_frame ().set_alpha (framealpha )
589555
590- tmp = self ._loc_used_default
591- self ._set_loc (loc )
592- self ._loc_used_default = tmp # ignore changes done by _set_loc
593-
594556 # figure out title fontsize:
595557 if title_fontsize is None :
596558 title_fontsize = rcParams ['legend.title_fontsize' ]
@@ -611,10 +573,19 @@ def _set_artist_props(self, a):
611573 a .set_transform (self .get_transform ())
612574
613575 def _set_loc (self , loc ):
576+ if loc is None :
577+ loc = rcParams ["legend.loc" ]
578+ if not self .isaxes and loc in [0 , 'best' ]:
579+ loc = 'NE'
580+ if self .isaxes :
581+ loc = _map_loc_to_compass (loc , allowtuple = True , allowbest = True ,
582+ fallback = "best" , warnonly = True )
583+ else :
584+ loc = _map_loc_to_compass (loc , allowtuple = True , allowbest = False ,
585+ fallback = "NE" , warnonly = True )
614586 # find_offset function will be provided to _legend_box and
615587 # _legend_box will draw itself at the location of the return
616588 # value of the find_offset.
617- self ._loc_used_default = False
618589 self ._loc_real = loc
619590 self .stale = True
620591 self ._legend_box .set_offset (self ._findoffset )
@@ -624,12 +595,27 @@ def _get_loc(self):
624595
625596 _loc = property (_get_loc , _set_loc )
626597
598+ def set_loc (self , loc ):
599+ """
600+ Set the legend location. For possible values see the `~.Axes.legend`
601+ docstring.
602+ """
603+ self ._loc_used_default = False
604+ self ._set_loc (loc )
605+
606+ def get_loc (self ):
607+ """
608+ Get the legend location. This will be one of 'best', 'NE', 'NW',
609+ 'SW', 'SE', 'E', 'W', 'E', 'S', 'N', 'C' or a tuple of floats.
610+ """
611+ return self ._get_loc ()
612+
627613 def _findoffset (self , width , height , xdescent , ydescent , renderer ):
628614 "Helper function to locate the legend."
629615
630- if self ._loc == 0 : # "best".
616+ if self ._loc == 'best' : # "best".
631617 x , y = self ._find_best_position (width , height , renderer )
632- elif self ._loc in Legend . codes . values ( ): # Fixed location.
618+ elif isinstance ( self ._loc , str ): # Fixed location.
633619 bbox = Bbox .from_bounds (0 , 0 , width , height )
634620 x , y = self ._get_anchored_bbox (self ._loc , bbox ,
635621 self .get_bbox_to_anchor (),
@@ -1098,26 +1084,12 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
10981084 - parentbbox: a parent box which will contain the bbox. In
10991085 display coordinates.
11001086 """
1101- assert loc in range (1 , 11 ) # called only internally
1102-
1103- BEST , UR , UL , LL , LR , R , CL , CR , LC , UC , C = range (11 )
1104-
1105- anchor_coefs = {UR : "NE" ,
1106- UL : "NW" ,
1107- LL : "SW" ,
1108- LR : "SE" ,
1109- R : "E" ,
1110- CL : "W" ,
1111- CR : "E" ,
1112- LC : "S" ,
1113- UC : "N" ,
1114- C : "C" }
11151087
1116- c = anchor_coefs [ loc ]
1088+ assert loc in _valid_compass # as this is called only internally
11171089
11181090 fontsize = renderer .points_to_pixels (self ._fontsize )
11191091 container = parentbbox .padded (- (self .borderaxespad ) * fontsize )
1120- anchored_box = bbox .anchored (c , container = container )
1092+ anchored_box = bbox .anchored (loc , container = container )
11211093 return anchored_box .x0 , anchored_box .y0
11221094
11231095 def _find_best_position (self , width , height , renderer , consider = None ):
@@ -1140,10 +1112,10 @@ def _find_best_position(self, width, height, renderer, consider=None):
11401112
11411113 bbox = Bbox .from_bounds (0 , 0 , width , height )
11421114 if consider is None :
1143- consider = [self ._get_anchored_bbox (x , bbox ,
1115+ consider = [self ._get_anchored_bbox (loc , bbox ,
11441116 self .get_bbox_to_anchor (),
11451117 renderer )
1146- for x in range ( 1 , len ( self . codes )) ]
1118+ for loc in _valid_compass ]
11471119
11481120 candidates = []
11491121 for idx , (l , b ) in enumerate (consider ):
0 commit comments