def _update_positions(self, renderer): # called from renderer to allow more precise estimates of # widths and heights with get_window_extent if not len(self.legendHandles) and not len(self.texts): return def get_tbounds(text): #get text bounds in axes coords bbox = text.get_window_extent(renderer) bboxa = bbox.inverse_transformed(self.get_transform()) return bboxa.bounds hpos = [] for t, tabove in safezip(self.texts[1:], self.texts[:-1]): x, y = t.get_position() l, b, w, h = get_tbounds(tabove) b -= self.labelsep h += 2 * self.labelsep hpos.append((b, h)) t.set_position((x, b - 0.1 * h)) # now do the same for last line l, b, w, h = get_tbounds(self.texts[-1]) b -= self.labelsep h += 2 * self.labelsep hpos.append((b, h)) for handle, tup in safezip(self.legendHandles, hpos): y, h = tup if isinstance(handle, Line2D): ydata = y * np.ones(handle.get_xdata().shape, float) handle.set_ydata(ydata + h / 2.) handle._legmarker.set_ydata(ydata + h / 2.) elif isinstance(handle, Rectangle): handle.set_y(y + 1 / 4 * h) handle.set_height(h / 2) # Set the data for the legend patch bbox = self._get_handle_text_bbox(renderer) bbox = bbox.expanded(1 + self.pad, 1 + self.pad) l, b, w, h = bbox.bounds self.legendPatch.set_bounds(l, b, w, h) ox, oy = 0, 0 # center if iterable(self._loc) and len(self._loc) == 2: xo = self.legendPatch.get_x() yo = self.legendPatch.get_y() x, y = self._loc ox, oy = x - xo, y - yo elif self._loc == 0: # "best" ox, oy = self._find_best_position(w, h) else: x, y = self._loc_to_axes_coords(self._loc, w, h) ox, oy = x - l, y - b self._offset(ox, oy)
def _update_positions(self, renderer): # called from renderer to allow more precise estimates of # widths and heights with get_window_extent if not len(self.legendHandles) and not len(self.texts): return def get_tbounds(text): #get text bounds in axes coords bbox = text.get_window_extent(renderer) bboxa = bbox.inverse_transformed(self.get_transform()) return bboxa.bounds hpos = [] for t, tabove in safezip(self.texts[1:], self.texts[:-1]): x,y = t.get_position() l,b,w,h = get_tbounds(tabove) b -= self.labelsep h += 2*self.labelsep hpos.append( (b,h) ) t.set_position( (x, b-0.1*h) ) # now do the same for last line l,b,w,h = get_tbounds(self.texts[-1]) b -= self.labelsep h += 2*self.labelsep hpos.append( (b,h) ) for handle, tup in safezip(self.legendHandles, hpos): y,h = tup if isinstance(handle, Line2D): ydata = y*np.ones(handle.get_xdata().shape, float) handle.set_ydata(ydata+h/2.) handle._legmarker.set_ydata(ydata+h/2.) elif isinstance(handle, Rectangle): handle.set_y(y+1/4*h) handle.set_height(h/2) # Set the data for the legend patch bbox = self._get_handle_text_bbox(renderer) bbox = bbox.expanded(1 + self.pad, 1 + self.pad) l, b, w, h = bbox.bounds self.legendPatch.set_bounds(l, b, w, h) ox, oy = 0, 0 # center if iterable(self._loc) and len(self._loc)==2: xo = self.legendPatch.get_x() yo = self.legendPatch.get_y() x, y = self._loc ox, oy = x-xo, y-yo elif self._loc == 0: # "best" ox, oy = self._find_best_position(w, h) else: x, y = self._loc_to_axes_coords(self._loc, w, h) ox, oy = x-l, y-b self._offset(ox, oy)
def __init__(self, parent, handles, labels, loc = None, numpoints = None, # the number of points in the legend line prop = None, pad = None, # the fractional whitespace inside the legend border markerscale = None, # the relative size of legend markers vs. original # the following dimensions are in axes coords labelsep = None, # the vertical space between the legend entries handlelen = None, # the length of the legend lines handletextsep = None, # the space between the legend line and legend text axespad = None, # the border between the axes and legend edge shadow = None ): """ parent # the artist that contains the legend handles # a list of artists (lines, patches) to add to the legend labels # a list of strings to label the legend loc # a location code numpoints = 4 # the number of points in the legend line prop = FontProperties(size='smaller') # the font property pad = 0.2 # the fractional whitespace inside the legend border markerscale = 0.6 # the relative size of legend markers vs. original shadow # if True, draw a shadow behind legend The following dimensions are in axes coords labelsep = 0.005 # the vertical space between the legend entries handlelen = 0.05 # the length of the legend lines handletextsep = 0.02 # the space between the legend line and legend text axespad = 0.02 # the border between the axes and legend edge """ from axes import Axes # local import only to avoid circularity from figure import Figure # local import only to avoid circularity Artist.__init__(self) proplist=[numpoints, pad, markerscale, labelsep, handlelen, handletextsep, axespad, shadow] propnames=['numpoints', 'pad', 'markerscale', 'labelsep', 'handlelen', 'handletextsep', 'axespad', 'shadow'] for name, value in safezip(propnames,proplist): if value is None: value=rcParams["legend."+name] setattr(self,name,value) if self.numpoints <= 0: raise ValueError("numpoints must be >= 0; it was %d"% numpoints) if prop is None: self.prop=FontProperties(size=rcParams["legend.fontsize"]) else: self.prop=prop self.fontsize = self.prop.get_size_in_points() if isinstance(parent,Axes): self.isaxes = True self.set_figure(parent.figure) elif isinstance(parent,Figure): self.isaxes = False self.set_figure(parent) else: raise TypeError("Legend needs either Axes or Figure as parent") self.parent = parent self._offsetTransform = Affine2D() self._parentTransform = BboxTransformTo(parent.bbox) Artist.set_transform(self, self._offsetTransform + self._parentTransform) if loc is None: loc = rcParams["legend.loc"] if not self.isaxes and loc in [0,'best']: loc = 'upper right' if is_string_like(loc): if not self.codes.has_key(loc): if self.isaxes: warnings.warn('Unrecognized location "%s". Falling back on "best"; ' 'valid locations are\n\t%s\n' % (loc, '\n\t'.join(self.codes.keys()))) loc = 0 else: warnings.warn('Unrecognized location "%s". Falling back on "upper right"; ' 'valid locations are\n\t%s\n' % (loc, '\n\t'.join(self.codes.keys()))) loc = 1 else: loc = self.codes[loc] if not self.isaxes and loc == 0: warnings.warn('Automatic legend placement (loc="best") not implemented for figure legend. ' 'Falling back on "upper right".') loc = 1 self._loc = loc self.legendPatch = Rectangle( xy=(0.0, 0.0), width=0.5, height=0.5, facecolor='w', edgecolor='k', ) self._set_artist_props(self.legendPatch) # make a trial box in the middle of the axes. relocate it # based on it's bbox left, top = 0.5, 0.5 textleft = left+ self.handlelen+self.handletextsep self.texts = self._get_texts(labels, textleft, top) self.legendHandles = self._get_handles(handles, self.texts) self._drawFrame = True
def _get_handles(self, handles, texts): handles = list(handles) texts = list(texts) HEIGHT = self._approx_text_height() left = 0.5 ret = [] # the returned legend lines # we need to pad the text with empties for the numpoints=1 # centered marker proxy for handle, label in safezip(handles, texts): if self.numpoints > 1: xdata = np.linspace(left, left + self.handlelen, self.numpoints) xdata_marker = xdata elif self.numpoints == 1: xdata = np.linspace(left, left + self.handlelen, 2) xdata_marker = [left + 0.5*self.handlelen] x, y = label.get_position() x -= self.handlelen + self.handletextsep if isinstance(handle, Line2D): ydata = (y-HEIGHT/2)*np.ones(xdata.shape, float) legline = Line2D(xdata, ydata) legline.update_from(handle) self._set_artist_props(legline) # after update legline.set_clip_box(None) legline.set_clip_path(None) ret.append(legline) legline.set_marker('None') legline_marker = Line2D(xdata_marker, ydata[:len(xdata_marker)]) legline_marker.update_from(handle) legline_marker.set_linestyle('None') self._set_artist_props(legline_marker) # we don't want to add this to the return list because # the texts and handles are assumed to be in one-to-one # correpondence. legline._legmarker = legline_marker elif isinstance(handle, Patch): p = Rectangle(xy=(min(xdata), y-3/4*HEIGHT), width = self.handlelen, height=HEIGHT/2, ) p.update_from(handle) self._set_artist_props(p) p.set_clip_box(None) p.set_clip_path(None) ret.append(p) elif isinstance(handle, LineCollection): ydata = (y-HEIGHT/2)*np.ones(xdata.shape, float) legline = Line2D(xdata, ydata) self._set_artist_props(legline) legline.set_clip_box(None) legline.set_clip_path(None) lw = handle.get_linewidth()[0] dashes = handle.get_dashes()[0] color = handle.get_colors()[0] legline.set_color(color) legline.set_linewidth(lw) legline.set_dashes(dashes) ret.append(legline) elif isinstance(handle, RegularPolyCollection): if self.numpoints == 1: xdata = np.array([left]) p = Rectangle(xy=(min(xdata), y-3/4*HEIGHT), width = self.handlelen, height=HEIGHT/2, ) p.set_facecolor(handle._facecolors[0]) if handle._edgecolors != 'none' and len(handle._edgecolors): p.set_edgecolor(handle._edgecolors[0]) self._set_artist_props(p) p.set_clip_box(None) p.set_clip_path(None) ret.append(p) else: ret.append(None) return ret