1919import matplotlib .artist as martist
2020import matplotlib .text as mtext
2121import numpy as np
22+ from matplotlib .transforms import Bbox , TransformedBbox , BboxTransformTo
23+
24+ from matplotlib .font_manager import FontProperties
25+ from matplotlib .patches import FancyBboxPatch
26+ from matplotlib import rcParams
2227
2328from matplotlib .patches import bbox_artist as mbbox_artist
2429DEBUG = False
@@ -291,7 +296,7 @@ def get_extent_offsets(self, renderer):
291296 for c in self .get_visible_children ():
292297 if isinstance (c , PackerBase ) and c .mode == "expand" :
293298 c .set_width (self .width )
294-
299+
295300 whd_list = [c .get_extent (renderer ) for c in self .get_visible_children ()]
296301 whd_list = [(w , h , xd , (h - yd )) for w , h , xd , yd in whd_list ]
297302
@@ -752,7 +757,7 @@ def get_extent(self, renderer):
752757 self .ref_offset_transform .translate (- ub .x0 , - ub .y0 )
753758 # restor offset transform
754759 self .offset_transform .matrix_from_values (* _off )
755-
760+
756761 return ub .width , ub .height , 0. , 0.
757762
758763
@@ -767,14 +772,50 @@ def draw(self, renderer):
767772 bbox_artist (self , renderer , fill = False , props = dict (pad = 0. ))
768773
769774
770- from matplotlib .font_manager import FontProperties
771- from matplotlib .patches import FancyBboxPatch
772- from matplotlib import rcParams
773- from matplotlib .transforms import Bbox
774775
775776class AnchoredOffsetbox (OffsetBox ):
777+ """
778+ An offset box placed according to the legend location
779+ loc. AnchoredOffsetbox has a single child. When multiple children
780+ is needed, use other OffsetBox class to enlose them. By default,
781+ the offset box is anchored against its parent axes. You may
782+ explicitly specify the bbox_to_anchor.
783+ """
784+
776785 def __init__ (self , loc , pad = 0.4 , borderpad = 0.5 ,
777- child = None , prop = None , frameon = True ):
786+ child = None , prop = None , frameon = True ,
787+ bbox_to_anchor = None ,
788+ bbox_transform = None ):
789+ """
790+ loc is a string or an integer specifying the legend location.
791+ The valid location codes are::
792+
793+ 'upper right' : 1,
794+ 'upper left' : 2,
795+ 'lower left' : 3,
796+ 'lower right' : 4,
797+ 'right' : 5,
798+ 'center left' : 6,
799+ 'center right' : 7,
800+ 'lower center' : 8,
801+ 'upper center' : 9,
802+ 'center' : 10,
803+
804+
805+ pad : pad around the child for drawing a frame. given in
806+ fraction of fontsize.
807+
808+ borderpad : pad between offsetbox frame and the bbox_to_anchor,
809+
810+ child : OffsetBox instance that will be anchored.
811+
812+ prop : font property. This is only used as a reference for paddings.
813+
814+ frameon : draw a frame box if True.
815+
816+ bbox_to_anchor : bbox to anchor. If None, use axes.bbox.
817+
818+ """
778819
779820 super (AnchoredOffsetbox , self ).__init__ ()
780821
@@ -788,7 +829,7 @@ def __init__(self, loc, pad=0.4, borderpad=0.5,
788829 self .prop = FontProperties (size = rcParams ["legend.fontsize" ])
789830 else :
790831 self .prop = prop
791-
832+
792833 self .patch = FancyBboxPatch (
793834 xy = (0.0 , 0.0 ), width = 1. , height = 1. ,
794835 facecolor = 'w' , edgecolor = 'k' ,
@@ -797,48 +838,121 @@ def __init__(self, loc, pad=0.4, borderpad=0.5,
797838 )
798839 self .patch .set_boxstyle ("square" ,pad = 0 )
799840 self ._drawFrame = frameon
841+ #self._parent_bbox = bbox_to_anchor
842+ self .set_bbox_to_anchor (bbox_to_anchor , bbox_transform )
843+
844+
845+
800846
801847 def set_child (self , child ):
848+ "set the child to be anchored"
802849 self ._child = child
803850
851+ def get_child (self ):
852+ "return the child"
853+ return self ._child
854+
804855 def get_children (self ):
856+ "return the list of children"
805857 return [self ._child ]
806858
807- def get_child (self ):
808- return self ._child
809859
810860 def get_extent (self , renderer ):
861+ """
862+ return the extent of the artist. The extent of the child
863+ added with the pad is returned
864+ """
811865 w , h , xd , yd = self .get_child ().get_extent (renderer )
812866 fontsize = renderer .points_to_pixels (self .prop .get_size_in_points ())
813867 pad = self .pad * fontsize
814868
815869 return w + 2 * pad , h + 2 * pad , xd + pad , yd + pad
816870
871+
872+ def get_bbox_to_anchor (self ):
873+ """
874+ return the bbox that the legend will be anchored
875+ """
876+ if self ._bbox_to_anchor is None :
877+ return self .axes .bbox
878+ else :
879+ transform = self ._bbox_to_anchor_transform
880+ if transform is None :
881+ transform = BboxTransformTo (self .axes .bbox )
882+
883+ return TransformedBbox (self ._bbox_to_anchor ,
884+ transform )
885+
886+
887+
888+ def set_bbox_to_anchor (self , bbox , transform = None ):
889+ """
890+ set the bbox that the child will be anchored.
891+
892+ *bbox* can be a Bbox instance, a list of [left, bottom, width,
893+ height], or a list of [left, bottom] where the width and
894+ height will be assumed to be zero. The bbox will be
895+ transformed to display coordinate by the given transform. If
896+ transform is None, axes.transAxes will be use.
897+ """
898+ if bbox is None :
899+ self ._bbox_to_anchor = None
900+ elif isinstance (bbox , Bbox ):
901+ self ._bbox_to_anchor = bbox
902+ else :
903+ try :
904+ l = len (bbox )
905+ except TypeError :
906+ raise ValueError ("Invalid argument for bbox : %s" % str (bbox ))
907+
908+ if l == 2 :
909+ bbox = [bbox [0 ], bbox [1 ], 0 , 0 ]
910+
911+ self ._bbox_to_anchor = Bbox .from_bounds (* bbox )
912+
913+ self ._bbox_to_anchor_transform = transform
914+
915+
817916 def get_window_extent (self , renderer ):
818917 '''
819918 get the bounding box in display space.
820919 '''
920+ self ._update_offset_func (renderer )
821921 w , h , xd , yd = self .get_extent (renderer )
822922 ox , oy = self .get_offset (w , h , xd , yd )
823923 return Bbox .from_bounds (ox - xd , oy - yd , w , h )
824924
825- def draw (self , renderer ):
826-
827- if not self .get_visible (): return
828925
829- fontsize = renderer .points_to_pixels (self .prop .get_size_in_points ())
926+ def _update_offset_func (self , renderer , fontsize = None ):
927+ """
928+ Update the offset func which depends on the dpi of the
929+ renderer (because of the padding).
930+ """
931+ if fontsize is None :
932+ fontsize = renderer .points_to_pixels (self .prop .get_size_in_points ())
830933
831934 def _offset (w , h , xd , yd , fontsize = fontsize , self = self ):
832935 bbox = Bbox .from_bounds (0 , 0 , w , h )
833936 borderpad = self .borderpad * fontsize
937+ bbox_to_anchor = self .get_bbox_to_anchor ()
938+
834939 x0 , y0 = self ._get_anchored_bbox (self .loc ,
835940 bbox ,
836- self . axes . bbox ,
941+ bbox_to_anchor ,
837942 borderpad )
838943 return x0 + xd , y0 + yd
839944
840945 self .set_offset (_offset )
841946
947+
948+ def draw (self , renderer ):
949+ "draw the artist"
950+
951+ if not self .get_visible (): return
952+
953+ fontsize = renderer .points_to_pixels (self .prop .get_size_in_points ())
954+ self ._update_offset_func (renderer , fontsize )
955+
842956 if self ._drawFrame :
843957 # update the location and size of the legend
844958 bbox = self .get_window_extent (renderer )
@@ -860,6 +974,10 @@ def _offset(w, h, xd, yd, fontsize=fontsize, self=self):
860974
861975
862976 def _get_anchored_bbox (self , loc , bbox , parentbbox , borderpad ):
977+ """
978+ return the position of the bbox anchored at the parentbbox
979+ with the loc code, with the borderpad.
980+ """
863981 assert loc in range (1 ,11 ) # called only internally
864982
865983 BEST , UR , UL , LL , LR , R , CL , CR , LC , UC , C = range (11 )
0 commit comments