From: <lee...@us...> - 2009-02-05 04:47:35
|
Revision: 6879 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6879&view=rev Author: leejjoon Date: 2009-02-05 04:47:26 +0000 (Thu, 05 Feb 2009) Log Message: ----------- some reorganization of the legend code. Modified Paths: -------------- trunk/matplotlib/CHANGELOG trunk/matplotlib/lib/matplotlib/legend.py trunk/matplotlib/lib/matplotlib/offsetbox.py Added Paths: ----------- trunk/matplotlib/examples/pylab_examples/anchored_text.py Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2009-02-04 21:48:49 UTC (rev 6878) +++ trunk/matplotlib/CHANGELOG 2009-02-05 04:47:26 UTC (rev 6879) @@ -1,3 +1,6 @@ +2009-02-04 Some reorgnization of the legend code. anchored_text.py + added as an example. - JJL + 2009-02-04 Add extent keyword arg to hexbin - ADS 2009-02-04 Fix bug in mathtext related to \dots and \ldots - MGD Added: trunk/matplotlib/examples/pylab_examples/anchored_text.py =================================================================== --- trunk/matplotlib/examples/pylab_examples/anchored_text.py (rev 0) +++ trunk/matplotlib/examples/pylab_examples/anchored_text.py 2009-02-05 04:47:26 UTC (rev 6879) @@ -0,0 +1,183 @@ +""" +Place a text (or any offsetbox artist) at the corner of the axes, like a lenged. +""" + +from matplotlib.offsetbox import TextArea, OffsetBox, DrawingArea +from matplotlib.transforms import Bbox +from matplotlib.font_manager import FontProperties +from matplotlib import rcParams +from matplotlib.patches import FancyBboxPatch +from matplotlib.patches import Circle + + +class AnchoredOffsetbox(OffsetBox): + def __init__(self, loc, pad=0.4, borderpad=0.5, + child=None, fontsize=None, frameon=True): + + super(AnchoredOffsetbox, self).__init__() + + self.set_child(child) + + self.loc = loc + self.borderpad=borderpad + self.pad = pad + + if fontsize is None: + prop=FontProperties(size=rcParams["legend.fontsize"]) + self._fontsize = prop.get_size_in_points() + else: + self._fontsize = fontsize + + + + self.patch = FancyBboxPatch( + xy=(0.0, 0.0), width=1., height=1., + facecolor='w', edgecolor='k', + mutation_scale=self._fontsize, + snap=True + ) + self.patch.set_boxstyle("square",pad=0) + self._drawFrame = frameon + + def set_child(self, child): + self._child = child + + def get_children(self): + return [self._child] + + def get_child(self): + return self._child + + def get_extent(self, renderer): + w, h, xd, yd = self.get_child().get_extent(renderer) + fontsize = renderer.points_to_pixels(self._fontsize) + pad = self.pad * fontsize + + return w+2*pad, h+2*pad, xd+pad, yd+pad + + def get_window_extent(self, renderer): + ''' + get the bounding box in display space. + ''' + w, h, xd, yd = self.get_extent(renderer) + ox, oy = self.get_offset(w, h, xd, yd) + return Bbox.from_bounds(ox-xd, oy-yd, w, h) + + def draw(self, renderer): + + if not self.get_visible(): return + + fontsize = renderer.points_to_pixels(self._fontsize) + + def _offset(w, h, xd, yd, fontsize=fontsize, self=self): + bbox = Bbox.from_bounds(0, 0, w, h) + borderpad = self.borderpad*fontsize + x0, y0 = self._get_anchored_bbox(self.loc, + bbox, + self.axes.bbox, + borderpad) + return x0+xd, y0+yd + + self.set_offset(_offset) + + if self._drawFrame: + # update the location and size of the legend + bbox = self.get_window_extent(renderer) + self.patch.set_bounds(bbox.x0, bbox.y0, + bbox.width, bbox.height) + + self.patch.set_mutation_scale(fontsize) + + self.patch.draw(renderer) + + + width, height, xdescent, ydescent = self.get_extent(renderer) + + px, py = self.get_offset(width, height, xdescent, ydescent) + + self.get_child().set_offset((px, py)) + self.get_child().draw(renderer) + + + + def _get_anchored_bbox(self, loc, bbox, parentbbox, borderpad): + assert loc in range(1,11) # called only internally + + BEST, UR, UL, LL, LR, R, CL, CR, LC, UC, C = range(11) + + anchor_coefs={UR:"NE", + UL:"NW", + LL:"SW", + LR:"SE", + R:"E", + CL:"W", + CR:"E", + LC:"S", + UC:"N", + C:"C"} + + c = anchor_coefs[loc] + + container = parentbbox.padded(-borderpad) + anchored_box = bbox.anchored(c, container=container) + return anchored_box.x0, anchored_box.y0 + + +class AnchoredText(AnchoredOffsetbox): + def __init__(self, s, loc, pad=0.4, borderpad=0.5, prop=None, frameon=True): + + self.txt = TextArea(s, + minimumdescent=False) + + + if prop is None: + self.prop=FontProperties(size=rcParams["legend.fontsize"]) + else: + self.prop=prop + + + super(AnchoredText, self).__init__(loc, pad=pad, borderpad=borderpad, + child=self.txt, + fontsize=self.prop.get_size_in_points(), + frameon=frameon) + + +class AnchoredDrawingArea(AnchoredOffsetbox): + def __init__(self, width, height, xdescent, ydescent, + loc, pad=0.4, borderpad=0.5, fontsize=None, frameon=True): + + self.da = DrawingArea(width, height, xdescent, ydescent, clip=True) + + super(AnchoredDrawingArea, self).__init__(loc, pad=pad, borderpad=borderpad, + child=self.da, + fontsize=fontsize, + frameon=frameon) + + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + #ax = plt.subplot(1,1,1) + plt.clf() + plt.cla() + plt.draw() + ax = plt.gca() + #ax.set_aspect(1.) + + at = AnchoredText("Figure 1(a)", loc=2, frameon=False) + ax.add_artist(at) + + ada = AnchoredDrawingArea(20, 20, 0, 0, loc=3, pad=0., frameon=False) + + p = Circle((10, 10), 10) + ada.da.add_artist(p) + ax.add_artist(ada) + + ax.plot([0,1]) + plt.draw() + + plt.show() + + + Modified: trunk/matplotlib/lib/matplotlib/legend.py =================================================================== --- trunk/matplotlib/lib/matplotlib/legend.py 2009-02-04 21:48:49 UTC (rev 6878) +++ trunk/matplotlib/lib/matplotlib/legend.py 2009-02-05 04:47:26 UTC (rev 6879) @@ -34,7 +34,7 @@ from matplotlib.collections import LineCollection, RegularPolyCollection from matplotlib.transforms import Bbox -from matplotlib.offsetbox import HPacker, VPacker, PackerBase, TextArea, DrawingArea +from matplotlib.offsetbox import HPacker, VPacker, TextArea, DrawingArea class Legend(Artist): @@ -138,7 +138,7 @@ ================ ================================================================== The dimensions of pad and spacing are given as a fraction of the -fontsize. Values from rcParams will be used if None. +_fontsize. Values from rcParams will be used if None. """ from matplotlib.axes import Axes # local import only to avoid circularity from matplotlib.figure import Figure # local import only to avoid circularity @@ -149,7 +149,7 @@ self.prop=FontProperties(size=rcParams["legend.fontsize"]) else: self.prop=prop - self.fontsize = self.prop.get_size_in_points() + self._fontsize = self.prop.get_size_in_points() propnames=['numpoints', 'markerscale', 'shadow', "columnspacing", "scatterpoints"] @@ -175,7 +175,7 @@ # conversion factor bbox = parent.bbox - axessize_fontsize = min(bbox.width, bbox.height)/self.fontsize + axessize_fontsize = min(bbox.width, bbox.height)/self._fontsize for k, v in deprecated_kwds.items(): # use deprecated value if not None and if their newer @@ -253,7 +253,7 @@ self.legendPatch = FancyBboxPatch( xy=(0.0, 0.0), width=1., height=1., facecolor='w', edgecolor='k', - mutation_scale=self.fontsize, + mutation_scale=self._fontsize, snap=True ) @@ -276,7 +276,7 @@ # init with null renderer self._init_legend_box(handles, labels) - self._last_fontsize_points = self.fontsize + self._last_fontsize_points = self._fontsize def _set_artist_props(self, a): @@ -313,7 +313,6 @@ "Draw everything that belongs to the legend" if not self.get_visible(): return - self._update_legend_box(renderer) renderer.open_group('legend') @@ -330,7 +329,7 @@ self._legend_box.set_offset(findoffset) - fontsize = renderer.points_to_pixels(self.fontsize) + fontsize = renderer.points_to_pixels(self._fontsize) # if mode == fill, set the width of the legend_box to the # width of the paret (minus pads) @@ -363,9 +362,9 @@ the legend handle. """ if renderer is None: - return self.fontsize + return self._fontsize else: - return renderer.points_to_pixels(self.fontsize) + return renderer.points_to_pixels(self._fontsize) def _init_legend_box(self, handles, labels): @@ -376,7 +375,7 @@ drawing time. """ - fontsize = self.fontsize + fontsize = self._fontsize # legend_box is a HPacker, horizontally packed with # columns. Each column is a VPacker, vertically packed with @@ -415,9 +414,6 @@ # (0, -descent, width, height). And their corrdinates should # be given in the display coordinates. - # NOTE : the coordinates will be updated again in - # _update_legend_box() method. - # The transformation of each handle will be automatically set # to self.get_trasnform(). If the artist does not uses its # default trasnform (eg, Collections), you need to @@ -567,9 +563,9 @@ sep = self.columnspacing*fontsize self._legend_box = HPacker(pad=self.borderpad*fontsize, - sep=sep, align="baseline", - mode=mode, - children=columnbox) + sep=sep, align="baseline", + mode=mode, + children=columnbox) self._legend_box.set_figure(self.figure) @@ -577,97 +573,6 @@ self.legendHandles = handle_list - - - def _update_legend_box(self, renderer): - """ - Update the dimension of the legend_box. This is required - becuase the paddings, the hadle size etc. depends on the dpi - of the renderer. - """ - - # fontsize in points. - fontsize = renderer.points_to_pixels(self.fontsize) - - if self._last_fontsize_points == fontsize: - # no update is needed - return - - # each handle needs to be drawn inside a box of - # (x, y, w, h) = (0, -descent, width, height). - # And their corrdinates should be given in the display coordinates. - - # The approximate height and descent of text. These values are - # only used for plotting the legend handle. - height = self._approx_text_height(renderer) * 0.7 - descent = 0. - - for handle in self.legendHandles: - if isinstance(handle, RegularPolyCollection): - npoints = self.scatterpoints - else: - npoints = self.numpoints - if npoints > 1: - # we put some pad here to compensate the size of the - # marker - xdata = np.linspace(0.3*fontsize, - (self.handlelength-0.3)*fontsize, - npoints) - xdata_marker = xdata - elif npoints == 1: - xdata = np.linspace(0, self.handlelength*fontsize, 2) - xdata_marker = [0.5*self.handlelength*fontsize] - - if isinstance(handle, Line2D): - legline = handle - ydata = ((height-descent)/2.)*np.ones(xdata.shape, float) - legline.set_data(xdata, ydata) - - # if a line collection is added, the legmarker attr is - # not set so we don't need to handle it - if hasattr(handle, "_legmarker"): - legline_marker = legline._legmarker - legline_marker.set_data(xdata_marker, ydata[:len(xdata_marker)]) - - elif isinstance(handle, Patch): - p = handle - p.set_bounds(0., 0., - self.handlelength*fontsize, - (height-descent), - ) - - elif isinstance(handle, RegularPolyCollection): - - p = handle - ydata = height*self._scatteryoffsets - p.set_offsets(zip(xdata_marker,ydata)) - - - # correction factor - cor = fontsize / self._last_fontsize_points - - # helper function to iterate over all children - def all_children(parent): - yield parent - for c in parent.get_children(): - for cc in all_children(c): yield cc - - - #now update paddings - for box in all_children(self._legend_box): - if isinstance(box, PackerBase): - box.pad = box.pad * cor - box.sep = box.sep * cor - - elif isinstance(box, DrawingArea): - box.width = self.handlelength*fontsize - box.height = height - box.xdescent = 0. - box.ydescent=descent - - self._last_fontsize_points = fontsize - - def _auto_legend_data(self): """ Returns list of vertices and extents covered by the plot. @@ -769,7 +674,7 @@ c = anchor_coefs[loc] - fontsize = renderer.points_to_pixels(self.fontsize) + fontsize = renderer.points_to_pixels(self._fontsize) container = parentbbox.padded(-(self.borderaxespad) * fontsize) anchored_box = bbox.anchored(c, container=container) return anchored_box.x0, anchored_box.y0 Modified: trunk/matplotlib/lib/matplotlib/offsetbox.py =================================================================== --- trunk/matplotlib/lib/matplotlib/offsetbox.py 2009-02-04 21:48:49 UTC (rev 6878) +++ trunk/matplotlib/lib/matplotlib/offsetbox.py 2009-02-05 04:47:26 UTC (rev 6879) @@ -38,7 +38,7 @@ total width and the x-offset positions of each items according to *mode*. xdescent is analagous to the usual descent, but along the x-direction. xdescent values are currently ignored. - + *wd_list* : list of (width, xdescent) of boxes to be packed. *sep* : spacing between boxes *total* : Intended total length. None if not used. @@ -47,14 +47,14 @@ w_list, d_list = zip(*wd_list) # d_list is currently not used. - + if mode == "fixed": offsets_ = np.add.accumulate([0]+[w + sep for w in w_list]) offsets = offsets_[:-1] if total is None: total = offsets_[-1] - sep - + return total, offsets elif mode == "expand": @@ -86,7 +86,7 @@ total width and the offset positions of each items according to *mode*. xdescent is analagous to the usual descent, but along the x-direction. xdescent values are currently ignored. - + *hd_list* : list of (width, xdescent) of boxes to be aligned. *sep* : spacing between boxes *height* : Intended total length. None if not used. @@ -120,13 +120,13 @@ class OffsetBox(martist.Artist): """ The OffsetBox is a simple container artist. The child artist are meant - to be drawn at a relative position to its parent. + to be drawn at a relative position to its parent. """ def __init__(self, *args, **kwargs): super(OffsetBox, self).__init__(*args, **kwargs) - - self._children = [] + + self._children = [] self._offset = (0, 0) def set_figure(self, fig): @@ -138,7 +138,7 @@ martist.Artist.set_figure(self, fig) for c in self.get_children(): c.set_figure(fig) - + def set_offset(self, xy): """ Set the offset @@ -173,7 +173,7 @@ accepts float """ self.height = height - + def get_children(self): """ Return a list of artists it contains. @@ -213,8 +213,8 @@ c.draw(renderer) bbox_artist(self, renderer, fill=False, props=dict(pad=0.)) - + class PackerBase(OffsetBox): def __init__(self, pad=None, sep=None, width=None, height=None, align=None, mode=None, @@ -224,8 +224,14 @@ *sep* : spacing between items *width*, *height* : width and height of the container box. calculated if None. - *align* : alignment of boxes + *align* : alignment of boxes. Can be one of 'top', 'bottom', + 'left', 'right', 'center' and 'baseline' *mode* : packing mode + + .. note:: + *pad* and *sep* need to given in points and will be + scale with the renderer dpi, while *width* and *hight* + need to be in pixels. """ super(PackerBase, self).__init__() @@ -254,9 +260,14 @@ calculated if None. *align* : alignment of boxes *mode* : packing mode + + .. note:: + *pad* and *sep* need to given in points and will be + scale with the renderer dpi, while *width* and *hight* + need to be in pixels. """ super(VPacker, self).__init__(pad, sep, width, height, - align, mode, + align, mode, children) @@ -266,6 +277,10 @@ update offset of childrens and return the extents of the box """ + dpicor = renderer.points_to_pixels(1.) + pad = self.pad * dpicor + sep = self.sep * dpicor + whd_list = [c.get_extent(renderer) for c in self.get_children()] whd_list = [(w, h, xd, (h-yd)) for w, h, xd, yd in whd_list] @@ -277,8 +292,8 @@ pack_list = [(h, yd) for w,h,xd,yd in whd_list] height, yoffsets_ = _get_packed_offsets(pack_list, self.height, - self.sep, self.mode) - + sep, self.mode) + yoffsets = yoffsets_ + [yd for w,h,xd,yd in whd_list] ydescent = height - yoffsets[0] yoffsets = height - yoffsets @@ -286,8 +301,9 @@ #w, h, xd, h_yd = whd_list[-1] yoffsets = yoffsets - ydescent - return width + 2*self.pad, height + 2*self.pad, \ - xdescent+self.pad, ydescent+self.pad, \ + + return width + 2*pad, height + 2*pad, \ + xdescent+pad, ydescent+pad, \ zip(xoffsets, yoffsets) @@ -296,7 +312,7 @@ The HPacker has its children packed horizontally. It automatically adjust the relative postisions of children in the drawing time. """ - def __init__(self, pad=None, sep=None, width=None, height=None, + def __init__(self, pad=None, sep=None, width=None, height=None, align="baseline", mode="fixed", children=None): """ @@ -306,6 +322,11 @@ calculated if None. *align* : alignment of boxes *mode* : packing mode + + .. note:: + *pad* and *sep* need to given in points and will be + scale with the renderer dpi, while *width* and *hight* + need to be in pixels. """ super(HPacker, self).__init__(pad, sep, width, height, align, mode, children) @@ -316,14 +337,18 @@ update offset of childrens and return the extents of the box """ + dpicor = renderer.points_to_pixels(1.) + pad = self.pad * dpicor + sep = self.sep * dpicor + whd_list = [c.get_extent(renderer) for c in self.get_children()] if self.height is None: - height_descent = max([h-yd for w,h,xd,yd in whd_list]) + height_descent = max([h-yd for w,h,xd,yd in whd_list]) ydescent = max([yd for w,h,xd,yd in whd_list]) height = height_descent + ydescent else: - height = self.height - 2*self._pad # width w/o pad + height = self.height - 2*pad # width w/o pad hd_list = [(h, yd) for w, h, xd, yd in whd_list] height, ydescent, yoffsets = _get_aligned_offsets(hd_list, @@ -333,26 +358,26 @@ pack_list = [(w, xd) for w,h,xd,yd in whd_list] width, xoffsets_ = _get_packed_offsets(pack_list, self.width, - self.sep, self.mode) + sep, self.mode) xoffsets = xoffsets_ + [xd for w,h,xd,yd in whd_list] xdescent=whd_list[0][2] xoffsets = xoffsets - xdescent - - return width + 2*self.pad, height + 2*self.pad, \ - xdescent + self.pad, ydescent + self.pad, \ + + return width + 2*pad, height + 2*pad, \ + xdescent + pad, ydescent + pad, \ zip(xoffsets, yoffsets) - + class DrawingArea(OffsetBox): """ The DrawingArea can contain any Artist as a child. The DrawingArea has a fixed width and height. The position of children relative to the parent is fixed. """ - + def __init__(self, width, height, xdescent=0., ydescent=0., clip=True): """ @@ -371,13 +396,16 @@ self.offset_transform.clear() self.offset_transform.translate(0, 0) + self.dpi_transform = mtransforms.Affine2D() + + def get_transform(self): """ Return the :class:`~matplotlib.transforms.Transform` applied to the children """ - return self.offset_transform + return self.dpi_transform + self.offset_transform def set_transform(self, t): """ @@ -404,7 +432,7 @@ """ return self._offset - + def get_window_extent(self, renderer): ''' get the bounding box in display space. @@ -418,10 +446,13 @@ """ Return with, height, xdescent, ydescent of box """ - return self.width, self.height, self.xdescent, self.ydescent + dpi_cor = renderer.points_to_pixels(1.) + return self.width*dpi_cor, self.height*dpi_cor, \ + self.xdescent*dpi_cor, self.ydescent*dpi_cor + def add_artist(self, a): 'Add any :class:`~matplotlib.artist.Artist` to the container box' self._children.append(a) @@ -433,6 +464,10 @@ Draw the children """ + dpi_cor = renderer.points_to_pixels(1.) + self.dpi_transform.clear() + self.dpi_transform.scale(dpi_cor, dpi_cor) + for c in self._children: c.draw(renderer) @@ -448,7 +483,7 @@ """ - + def __init__(self, s, textprops=None, multilinebaseline=None, @@ -473,8 +508,8 @@ OffsetBox.__init__(self) self._children = [self._text] - + self.offset_transform = mtransforms.Affine2D() self.offset_transform.clear() self.offset_transform.translate(0, 0) @@ -483,8 +518,8 @@ self._multilinebaseline = multilinebaseline self._minimumdescent = minimumdescent - + def set_multilinebaseline(self, t): """ Set multilinebaseline . @@ -507,7 +542,7 @@ """ Set minimumdescent . - If True, extent of the single line text is adjusted so that + If True, extent of the single line text is adjusted so that it has minimum descent of "p" """ self._minimumdescent = t @@ -545,7 +580,7 @@ """ return self._offset - + def get_window_extent(self, renderer): ''' get the bounding box in display space. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |