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.
------------------------------------------------------------------------------
Create and Deploy Rich Internet Apps outside the browser with Adobe(R)AIR(TM)
software. With Adobe AIR, Ajax developers can use existing skills and code to
build responsive, highly engaging applications that combine the power of local
resources and data with the reach of the web. Download the Adobe AIR SDK and
Ajax docs to start building applications today-http://p.sf.net/sfu/adobe-com
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins