3434from matplotlib .collections import LineCollection , RegularPolyCollection
3535from matplotlib .transforms import Bbox
3636
37- from matplotlib .offsetbox import HPacker , VPacker , TextArea , DrawingArea
37+ from matplotlib .offsetbox import HPacker , VPacker , PackerBase , TextArea , DrawingArea
3838
3939
4040class Legend (Artist ):
@@ -207,11 +207,6 @@ def __init__(self, parent, handles, labels,
207207 reps = int (self .numpoints / len (self ._scatteryoffsets )) + 1
208208 self ._scatteryoffsets = np .tile (self ._scatteryoffsets , reps )[:self .scatterpoints ]
209209
210- # handles & labels (which can be iterators) need to be
211- # explicitly converted to list.
212- self ._handles_labels = list (handles ), list (labels )
213-
214-
215210 # _legend_box is an OffsetBox instance that contains all
216211 # legend items and will be initialized from _init_legend_box()
217212 # method.
@@ -277,10 +272,11 @@ def __init__(self, parent, handles, labels,
277272 self ._set_artist_props (self .legendPatch )
278273
279274 self ._drawFrame = True
280-
275+
281276 # init with null renderer
282- #self._init_legend_box(handles, labels, None)
283- #self._legend_box.set_figure(self.figure)
277+ self ._init_legend_box (handles , labels )
278+
279+ self ._last_fontsize_points = self .fontsize
284280
285281
286282 def _set_artist_props (self , a ):
@@ -294,9 +290,9 @@ def _set_artist_props(self, a):
294290
295291 a .set_transform (self .get_transform ())
296292
297- def _findoffset_best (self , width , height , xdescent , ydescent ):
293+ def _findoffset_best (self , width , height , xdescent , ydescent , renderer ):
298294 "Heper function to locate the legend at its best position"
299- ox , oy = self ._find_best_position (width , height )
295+ ox , oy = self ._find_best_position (width , height , renderer )
300296 return ox + xdescent , oy + ydescent
301297
302298 def _findoffset_loc (self , width , height , xdescent , ydescent , renderer ):
@@ -317,23 +313,23 @@ def draw(self, renderer):
317313 "Draw everything that belongs to the legend"
318314 if not self .get_visible (): return
319315
320- # populate the legend_box with legend items.
321- handles , labels = self ._handles_labels
322- self ._init_legend_box (handles , labels , renderer )
323- self ._legend_box .set_figure (self .figure )
316+ self ._update_legend_box (renderer )
324317
325318 renderer .open_group ('legend' )
326319
327320 # find_offset function will be provided to _legend_box and
328321 # _legend_box will draw itself at the location of the return
329322 # value of the find_offset.
330323 if self ._loc == 0 :
331- self ._legend_box . set_offset ( self . _findoffset_best )
324+ _findoffset = self ._findoffset_best
332325 else :
333- def _findoffset_loc (width , height , xdescent , ydescent ):
334- return self ._findoffset_loc (width , height , xdescent , ydescent , renderer )
335- self ._legend_box .set_offset (_findoffset_loc )
326+ _findoffset = self ._findoffset_loc
336327
328+ def findoffset (width , height , xdescent , ydescent ):
329+ return _findoffset (width , height , xdescent , ydescent , renderer )
330+
331+ self ._legend_box .set_offset (findoffset )
332+
337333 fontsize = renderer .points_to_pixels (self .fontsize )
338334
339335 # if mode == fill, set the width of the legend_box to the
@@ -361,26 +357,26 @@ def _findoffset_loc(width, height, xdescent, ydescent):
361357 renderer .close_group ('legend' )
362358
363359
364- def _approx_text_height (self ):
360+ def _approx_text_height (self , renderer = None ):
365361 """
366362 Return the approximate height of the text. This is used to place
367363 the legend handle.
368364 """
369- return self .fontsize / 72.0 * self .figure .dpi
365+ if renderer is None :
366+ return self .fontsize
367+ else :
368+ return renderer .points_to_pixels (self .fontsize )
370369
371370
372- def _init_legend_box (self , handles , labels , renderer = None ):
371+ def _init_legend_box (self , handles , labels ):
373372 """
374373 Initiallize the legend_box. The legend_box is an instance of
375374 the OffsetBox, which is packed with legend handles and
376375 texts. Once packed, their location is calculated during the
377376 drawing time.
378377 """
379378
380- if renderer is None :
381- fontsize = self .fontsize
382- else :
383- fontsize = renderer .points_to_pixels (self .fontsize )
379+ fontsize = self .fontsize
384380
385381 # legend_box is a HPacker, horizontally packed with
386382 # columns. Each column is a VPacker, vertically packed with
@@ -415,9 +411,12 @@ def _init_legend_box(self, handles, labels, renderer=None):
415411 height = self ._approx_text_height () * 0.7
416412 descent = 0.
417413
418- # each handle needs to be drawn inside a box of
419- # (x, y, w, h) = (0, -descent, width, height).
420- # And their corrdinates should be given in the display coordinates.
414+ # each handle needs to be drawn inside a box of (x, y, w, h) =
415+ # (0, -descent, width, height). And their corrdinates should
416+ # be given in the display coordinates.
417+
418+ # NOTE : the coordinates will be updated again in
419+ # _update_legend_box() method.
421420
422421 # The transformation of each handle will be automatically set
423422 # to self.get_trasnform(). If the artist does not uses its
@@ -548,8 +547,8 @@ def _init_legend_box(self, handles, labels, renderer=None):
548547 for i0 , di in largecol + smallcol :
549548 # pack handleBox and labelBox into itemBox
550549 itemBoxes = [HPacker (pad = 0 ,
551- sep = self .handletextpad * fontsize ,
552- children = [h , t ], align = "baseline" )
550+ sep = self .handletextpad * fontsize ,
551+ children = [h , t ], align = "baseline" )
553552 for h , t in handle_label [i0 :i0 + di ]]
554553 # minimumdescent=False for the text of the last row of the column
555554 itemBoxes [- 1 ].get_children ()[1 ].set_minimumdescent (False )
@@ -572,10 +571,100 @@ def _init_legend_box(self, handles, labels, renderer=None):
572571 mode = mode ,
573572 children = columnbox )
574573
574+ self ._legend_box .set_figure (self .figure )
575+
575576 self .texts = text_list
576577 self .legendHandles = handle_list
577578
578579
580+
581+
582+ def _update_legend_box (self , renderer ):
583+ """
584+ Update the dimension of the legend_box. This is required
585+ becuase the paddings, the hadle size etc. depends on the dpi
586+ of the renderer.
587+ """
588+
589+ # fontsize in points.
590+ fontsize = renderer .points_to_pixels (self .fontsize )
591+
592+ if self ._last_fontsize_points == fontsize :
593+ # no update is needed
594+ return
595+
596+ # each handle needs to be drawn inside a box of
597+ # (x, y, w, h) = (0, -descent, width, height).
598+ # And their corrdinates should be given in the display coordinates.
599+
600+ # The approximate height and descent of text. These values are
601+ # only used for plotting the legend handle.
602+ height = self ._approx_text_height (renderer ) * 0.7
603+ descent = 0.
604+
605+ for handle in self .legendHandles :
606+ if isinstance (handle , RegularPolyCollection ):
607+ npoints = self .scatterpoints
608+ else :
609+ npoints = self .numpoints
610+ if npoints > 1 :
611+ # we put some pad here to compensate the size of the
612+ # marker
613+ xdata = np .linspace (0.3 * fontsize ,
614+ (self .handlelength - 0.3 )* fontsize ,
615+ npoints )
616+ xdata_marker = xdata
617+ elif npoints == 1 :
618+ xdata = np .linspace (0 , self .handlelength * fontsize , 2 )
619+ xdata_marker = [0.5 * self .handlelength * fontsize ]
620+
621+ if isinstance (handle , Line2D ):
622+ legline = handle
623+ ydata = ((height - descent )/ 2. )* np .ones (xdata .shape , float )
624+ legline .set_data (xdata , ydata )
625+
626+ legline_marker = legline ._legmarker
627+ legline_marker .set_data (xdata_marker , ydata [:len (xdata_marker )])
628+
629+ elif isinstance (handle , Patch ):
630+ p = handle
631+ p .set_bounds (0. , 0. ,
632+ self .handlelength * fontsize ,
633+ (height - descent ),
634+ )
635+
636+ elif isinstance (handle , RegularPolyCollection ):
637+
638+ p = handle
639+ ydata = height * self ._scatteryoffsets
640+ p .set_offsets (zip (xdata_marker ,ydata ))
641+
642+
643+ # correction factor
644+ cor = fontsize / self ._last_fontsize_points
645+
646+ # helper function to iterate over all children
647+ def all_children (parent ):
648+ yield parent
649+ for c in parent .get_children ():
650+ for cc in all_children (c ): yield cc
651+
652+
653+ #now update paddings
654+ for box in all_children (self ._legend_box ):
655+ if isinstance (box , PackerBase ):
656+ box .pad = box .pad * cor
657+ box .sep = box .sep * cor
658+
659+ elif isinstance (box , DrawingArea ):
660+ box .width = self .handlelength * fontsize
661+ box .height = height
662+ box .xdescent = 0.
663+ box .ydescent = descent
664+
665+ self ._last_fontsize_points = fontsize
666+
667+
579668 def _auto_legend_data (self ):
580669 """
581670 Returns list of vertices and extents covered by the plot.
@@ -683,7 +772,7 @@ def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer):
683772 return anchored_box .x0 , anchored_box .y0
684773
685774
686- def _find_best_position (self , width , height , consider = None ):
775+ def _find_best_position (self , width , height , renderer , consider = None ):
687776 """
688777 Determine the best location to place the legend.
689778
@@ -696,7 +785,7 @@ def _find_best_position(self, width, height, consider=None):
696785 verts , bboxes , lines = self ._auto_legend_data ()
697786
698787 bbox = Bbox .from_bounds (0 , 0 , width , height )
699- consider = [self ._get_anchored_bbox (x , bbox , self .parent .bbox ) for x in range (1 , len (self .codes ))]
788+ consider = [self ._get_anchored_bbox (x , bbox , self .parent .bbox , renderer ) for x in range (1 , len (self .codes ))]
700789
701790 #tx, ty = self.legendPatch.get_x(), self.legendPatch.get_y()
702791
0 commit comments