Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 09c9d33

Browse files
committed
Another attempt to fix dpi-dependent behavior of Legend
svn path=/branches/v0_98_5_maint/; revision=6640
1 parent ae27e0c commit 09c9d33

3 files changed

Lines changed: 173 additions & 74 deletions

File tree

CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
2008-12-16 Another attempt to fix dpi-dependent behavior of Legend. -JJL
2+
13
======================================================================
24
2008-12-16 Release 0.98.5.1 at r6636
35

lib/matplotlib/legend.py

Lines changed: 122 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from matplotlib.collections import LineCollection, RegularPolyCollection
3535
from 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

4040
class 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

Comments
 (0)