25
25
26
26
import numpy as np
27
27
28
- from matplotlib import cbook
29
28
from matplotlib import rcParams
30
29
from matplotlib import cbook , docstring
31
30
from 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 )
33
33
from matplotlib .font_manager import FontProperties
34
34
from matplotlib .lines import Line2D
35
35
from matplotlib .patches import Patch , Rectangle , Shadow , FancyBboxPatch
@@ -115,13 +115,14 @@ def _update_bbox_to_anchor(self, loc_in_canvas):
115
115
The location of the legend.
116
116
117
117
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.
120
121
121
122
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.
125
126
126
127
The string ``'center'`` places the legend at the center of the axes/figure.
127
128
@@ -136,23 +137,28 @@ def _update_bbox_to_anchor(self, loc_in_canvas):
136
137
137
138
For back-compatibility, ``'center right'`` (but no other location) can also
138
139
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).
156
162
157
163
158
164
bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats
@@ -324,21 +330,7 @@ class Legend(Artist):
324
330
Place a legend on the axes at location loc.
325
331
326
332
"""
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
+
342
334
zorder = 5
343
335
344
336
def __str__ (self ):
@@ -502,36 +494,6 @@ def __init__(self, parent, handles, labels,
502
494
raise TypeError ("Legend needs either Axes or Figure as parent" )
503
495
self .parent = parent
504
496
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
-
535
497
self ._mode = mode
536
498
self .set_bbox_to_anchor (bbox_to_anchor , bbox_transform )
537
499
@@ -577,6 +539,9 @@ def __init__(self, parent, handles, labels,
577
539
# init with null renderer
578
540
self ._init_legend_box (handles , labels , markerfirst )
579
541
542
+ # location must be set after _legend_box is created.
543
+ self ._loc = loc
544
+
580
545
# If shadow is activated use framealpha if not
581
546
# explicitly passed. See Issue 8943
582
547
if framealpha is None :
@@ -587,10 +552,6 @@ def __init__(self, parent, handles, labels,
587
552
else :
588
553
self .get_frame ().set_alpha (framealpha )
589
554
590
- tmp = self ._loc_used_default
591
- self ._set_loc (loc )
592
- self ._loc_used_default = tmp # ignore changes done by _set_loc
593
-
594
555
# figure out title fontsize:
595
556
if title_fontsize is None :
596
557
title_fontsize = rcParams ['legend.title_fontsize' ]
@@ -611,6 +572,16 @@ def _set_artist_props(self, a):
611
572
a .set_transform (self .get_transform ())
612
573
613
574
def _set_loc (self , loc ):
575
+ if loc is None :
576
+ loc = rcParams ["legend.loc" ]
577
+ if not self .isaxes and loc in [0 , 'best' ]:
578
+ loc = 'NE'
579
+ if self .isaxes :
580
+ loc = _map_loc_to_compass (loc , allowtuple = True , allowbest = True ,
581
+ fallback = "best" , warnonly = True )
582
+ else :
583
+ loc = _map_loc_to_compass (loc , allowtuple = True , allowbest = False ,
584
+ fallback = "NE" , warnonly = True )
614
585
# find_offset function will be provided to _legend_box and
615
586
# _legend_box will draw itself at the location of the return
616
587
# value of the find_offset.
@@ -624,12 +595,26 @@ def _get_loc(self):
624
595
625
596
_loc = property (_get_loc , _set_loc )
626
597
598
+ def set_loc (self , loc ):
599
+ """
600
+ Set the legend location. For possible values see the `~.Axes.legend`
601
+ docstring.
602
+ """
603
+ self ._set_loc (loc )
604
+
605
+ def get_loc (self ):
606
+ """
607
+ Get the legend location. This will be one of 'best', 'NE', 'NW',
608
+ 'SW', 'SE', 'E', 'W', 'E', 'S', 'N', 'C' or a tuple of floats.
609
+ """
610
+ return self ._get_loc ()
611
+
627
612
def _findoffset (self , width , height , xdescent , ydescent , renderer ):
628
613
"Helper function to locate the legend."
629
614
630
- if self ._loc == 0 : # "best".
615
+ if self ._loc == 'best' : # "best".
631
616
x , y = self ._find_best_position (width , height , renderer )
632
- elif self ._loc in Legend . codes . values ( ): # Fixed location.
617
+ elif isinstance ( self ._loc , str ): # Fixed location.
633
618
bbox = Bbox .from_bounds (0 , 0 , width , height )
634
619
x , y = self ._get_anchored_bbox (self ._loc , bbox ,
635
620
self .get_bbox_to_anchor (),
@@ -1098,26 +1083,12 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
1098
1083
- parentbbox: a parent box which will contain the bbox. In
1099
1084
display coordinates.
1100
1085
"""
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" }
1115
1086
1116
- c = anchor_coefs [ loc ]
1087
+ assert loc in _valid_compass # as this is called only internally
1117
1088
1118
1089
fontsize = renderer .points_to_pixels (self ._fontsize )
1119
1090
container = parentbbox .padded (- (self .borderaxespad ) * fontsize )
1120
- anchored_box = bbox .anchored (c , container = container )
1091
+ anchored_box = bbox .anchored (loc , container = container )
1121
1092
return anchored_box .x0 , anchored_box .y0
1122
1093
1123
1094
def _find_best_position (self , width , height , renderer , consider = None ):
@@ -1140,10 +1111,10 @@ def _find_best_position(self, width, height, renderer, consider=None):
1140
1111
1141
1112
bbox = Bbox .from_bounds (0 , 0 , width , height )
1142
1113
if consider is None :
1143
- consider = [self ._get_anchored_bbox (x , bbox ,
1114
+ consider = [self ._get_anchored_bbox (loc , bbox ,
1144
1115
self .get_bbox_to_anchor (),
1145
1116
renderer )
1146
- for x in range ( 1 , len ( self . codes )) ]
1117
+ for loc in _valid_compass ]
1147
1118
1148
1119
candidates = []
1149
1120
for idx , (l , b ) in enumerate (consider ):
0 commit comments