Revision: 6101
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6101&view=rev
Author: jdh2358
Date: 2008-09-17 20:52:08 +0000 (Wed, 17 Sep 2008)
Log Message:
-----------
committed Jae-Joons facy box and textbox patch
Modified Paths:
--------------
trunk/matplotlib/lib/matplotlib/patches.py
trunk/matplotlib/lib/matplotlib/text.py
Modified: trunk/matplotlib/lib/matplotlib/patches.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/patches.py 2008-09-17 15:47:02 UTC (rev
6100)
+++ trunk/matplotlib/lib/matplotlib/patches.py 2008-09-17 20:52:08 UTC (rev
6101)
@@ -1270,3 +1270,429 @@
for k in ('Rectangle', 'Circle', 'RegularPolygon', 'Polygon', 'Wedge', 'Arrow',
'FancyArrow', 'YAArrow', 'CirclePolygon', 'Ellipse', 'Arc'):
artist.kwdocd[k] = patchdoc
+
+
+
+
+
+
+
+class BboxTransmuterBase(object):
+ """
+ Bbox Transmuter Base class
+
+ BBoxTransmuterBase and its derivatives are used to make a fancy box
+ around a given rectangle. The __call__ method returns the Path of
+ the fancy box. This class is not an artist and actual drawing of the
+ fancy box is done by the FancyBboxPatch class.
+
+ """
+
+ # The derived classes are required to be able to be initialized
+ # w/o arguments, i.e., all its argument (except self) must have
+ # the default values.
+
+ def __init__(self):
+ super(BboxTransmuterBase, self).__init__()
+
+
+
+
+ def transmute(self, x0, y0, width, height, mutation_size):
+ """
+ The transmute method is a very core of the BboxTransmuter class
+ and must be overriden in the subclasses. It receives the
+ location and size of the rectangle, and the mutation_size, with
+ which the amound padding and etc. will be scaled. It returns a
+ Path instance.
+ """
+ raise NotImplementedError('Derived must override')
+
+
+
+ def __call__(self, x0, y0, width, height, mutation_size,
+ aspect_ratio=1.):
+ """
+ The __call__ method a thin wrapper around the transmute method
+ and take care of the aspect.
+ """
+ if aspect_ratio is not None:
+ # Squeeze the given height by the aspect_ratio
+ y0, height = y0/aspect_ratio, height/aspect_ratio
+ # call transmute method with squeezed height.
+ path = self.transmute(x0, y0, width, height, mutation_size)
+ vertices, codes = path.vertices, path.codes
+ # Restore the height
+ vertices[:,1] = vertices[:,1] * aspect_ratio
+ return Path(vertices, codes)
+ else:
+ return self.transmute(x0, y0, width, height, mutation_size)
+
+
+
+class SquareBoxTransmuter(BboxTransmuterBase):
+ """
+ Simple square box.
+
+ 'pad' :an amount of padding.
+ """
+
+ def __init__(self, pad=0.3):
+ self.pad = pad
+ super(SquareBoxTransmuter, self).__init__()
+
+ def transmute(self, x0, y0, width, height, mutation_size):
+
+ # padding
+ pad = mutation_size * self.pad
+
+ # width and height with padding added.
+ width, height = width + 2.*pad, \
+ height + 2.*pad,
+
+ # boundary of the padded box
+ x0, y0 = x0-pad, y0-pad,
+ x1, y1 = x0+width, y0 + height
+
+ cp = [(x0, y0), (x1, y0), (x1, y1), (x0, y1),
+ (x0, y0), (x0, y0)]
+
+ com = [Path.MOVETO,
+ Path.LINETO,
+ Path.LINETO,
+ Path.LINETO,
+ Path.LINETO,
+ Path.CLOSEPOLY]
+
+ path = Path(cp, com)
+
+ return path
+
+
+class RoundBoxTransmuter(BboxTransmuterBase):
+ """
+ A box with round corners.
+ """
+
+ def __init__(self, pad=0.3, rounding_size=None):
+ self.pad = pad
+ self.rounding_size = rounding_size
+ BboxTransmuterBase.__init__(self)
+
+ def transmute(self, x0, y0, width, height, mutation_size):
+
+ # padding
+ pad = mutation_size * self.pad
+
+ # size of the roudning corner
+ if self.rounding_size:
+ dr = mutation_size * self.rounding_size
+ else:
+ dr = pad
+
+ width, height = width + 2.*pad, \
+ height + 2.*pad,
+
+
+ x0, y0 = x0-pad, y0-pad,
+ x1, y1 = x0+width, y0 + height
+
+ # Round corners are implemented as quadratic bezier. eg.
+ # [(x0, y0-dr), (x0, y0), (x0+dr, y0)] for lower left corner.
+ cp = [(x0+dr, y0),
+ (x1-dr, y0),
+ (x1, y0), (x1, y0+dr),
+ (x1, y1-dr),
+ (x1, y1), (x1-dr, y1),
+ (x0+dr, y1),
+ (x0, y1), (x0, y1-dr),
+ (x0, y0+dr),
+ (x0, y0), (x0+dr, y0),
+ (x0+dr, y0)]
+
+ com = [Path.MOVETO,
+ Path.LINETO,
+ Path.CURVE3, Path.CURVE3,
+ Path.LINETO,
+ Path.CURVE3, Path.CURVE3,
+ Path.LINETO,
+ Path.CURVE3, Path.CURVE3,
+ Path.LINETO,
+ Path.CURVE3, Path.CURVE3,
+ Path.CLOSEPOLY]
+
+ path = Path(cp, com)
+
+ return path
+
+
+
+def _list_available_boxstyles(transmuters):
+ """ a helper function of the FancyBboxPatch to list the available
+ box styles. It inspects the arguments of the __init__ methods of
+ each classes and report them
+ """
+ import inspect
+ s = []
+ for name, cls in transmuters.items():
+ args, varargs, varkw, defaults = inspect.getargspec(cls.__init__)
+ args_string = ["%s=%s" % (argname, str(argdefault)) \
+ for argname, argdefault in zip(args[1:], defaults)]
+ s.append(",".join([name]+args_string))
+ return s
+
+
+
+
+
+class FancyBboxPatch(Patch):
+ """
+ Draw a fancy box around a rectangle with lower left at *xy*=(*x*,
+ *y*) with specified width and height.
+
+ FancyBboxPatch class is similar to Rectangle class, but it draws a
+ fancy box around the rectangle. The transfomation of the rectangle
+ box to the fancy box is delgated to the BoxTransmuterBase and its
+ derived classes. The "boxstyle" argument determins what kind of
+ fancy box will be drawn. In other words, it selects the
+ BboxTransmuter class to use, and sets optional attributes. A
+ custom BboxTransmuter can be used with bbox_transmuter argument
+ (should be an instance, not a class). mutation_scale determines
+ the overall size of the mutation (by which I mean the
+ transformation of the rectangle to the fancy path) and the
+ mutation_aspect determines the aspect-ratio of the mutation.
+
+ """
+
+ _fancy_bbox_transmuters = {"square":SquareBoxTransmuter,
+ "round":RoundBoxTransmuter,
+ }
+
+ def __str__(self):
+ return self.__class__.__name__ \
+ + "FancyBboxPatch(%g,%g;%gx%g)" % (self._x, self._y, self._width,
self._height)
+
+ def __init__(self, xy, width, height,
+ boxstyle="round",
+ bbox_transmuter=None,
+ mutation_scale=1.,
+ mutation_aspect=None,
+ **kwargs):
+ """
+ *xy*=lower left corner
+ *width*, *height*
+
+ The *boxstyle* describes how the fancy box will be drawn. It
+ should be one of the available boxstyle names, with optional
+ comma-separated attributes. These attributes are meant to be
+ scaled with the *mutation_scale*. Following box styles are
+ available.
+
+ %(AvailableBoxstyles)s
+
+ The boxstyle name can be "custom", in which case the
+ bbox_transmuter needs to be set, which should be an instance
+ of BboxTransmuterBase (or its derived).
+
+ *mutation_scale* : a value with which attributes of boxstyle
+ (e.g., pad) will be scaled. default=1.
+
+ *mutation_aspect* : The height of the rectangle will be
+ squeezed by this value before the mutation and the mutated
+ box will be stretched by the inverse of it. default=None.
+
+ Valid kwargs are:
+ %(Patch)s
+ """
+
+ Patch.__init__(self, **kwargs)
+
+ self._x = xy[0]
+ self._y = xy[1]
+ self._width = width
+ self._height = height
+
+ if boxstyle == "custom":
+ if bbox_transmuter is None:
+ raise ValueError("bbox_transmuter argument is needed with
custom boxstyle")
+ self._bbox_transmuter = bbox_transmuter
+ else:
+ self.set_boxstyle(boxstyle)
+
+ self._mutation_scale=mutation_scale
+ self._mutation_aspect=mutation_aspect
+
+
+ kwdoc = dict()
+ kwdoc["AvailableBoxstyles"]="\n".join([" - " + l \
+ for l in _list_available_boxstyles(_fancy_bbox_transmuters)])
+ kwdoc.update(artist.kwdocd)
+ __init__.__doc__ = cbook.dedent(__init__.__doc__) % kwdoc
+ del kwdoc
+
+ def list_available_boxstyles(cls):
+ return _list_available_boxstyles(cls._fancy_bbox_transmuters)
+
+
+ def set_boxstyle(self, boxstyle=None, **kw):
+ """
+ Set the box style.
+
+ *boxstyle* can be a string with boxstyle name with optional
+ comma-separated attributes. Alternatively, the attrs can
+ be probided as kewords.
+
+ set_boxstyle("round,pad=0.2")
+ set_boxstyle("round", pad=0.2)
+
+ Olf attrs simply are forgotten.
+
+ Without argument (or with boxstyle=None), it prints out
+ available box styles.
+ """
+
+ if boxstyle==None:
+ # print out available boxstyles and return.
+ print " Following box styles are available."
+ for l in self.list_available_boxstyles():
+ print " - " + l
+ return
+
+ # parse the boxstyle descrption (e.g. "round,pad=0.3")
+ bs_list = boxstyle.replace(" ","").split(",")
+ boxstyle_name = bs_list[0]
+ try:
+ bbox_transmuter_cls = self._fancy_bbox_transmuters[boxstyle_name]
+ except KeyError:
+ raise ValueError("Unknown Boxstyle : %s" % boxstyle_name)
+ try:
+ boxstyle_args_pair = [bs.split("=") for bs in bs_list[1:]]
+ boxstyle_args = dict([(k, float(v)) for k, v in
boxstyle_args_pair])
+ except ValueError:
+ raise ValueError("Incorrect Boxstyle argument : %s" % boxstyle)
+
+ boxstyle_args.update(kw)
+ self._bbox_transmuter = bbox_transmuter_cls(**boxstyle_args)
+
+
+ def set_mutation_scale(self, scale):
+ """
+ Set the mutation scale.
+
+ ACCEPTS: float
+ """
+ self._mutation_scale=scale
+
+ def get_mutation_scale(self):
+ """
+ Return the mutation scale.
+ """
+ return self._mutation_scale
+
+ def set_mutation_aspect(self, aspect):
+ """
+ Set the aspect ratio of the bbox mutation.
+
+ ACCEPTS: float
+ """
+ self._mutation_aspect=aspect
+
+ def get_mutation_aspect(self):
+ """
+ Return the aspect ratio of the bbox mutation.
+ """
+ return self._mutation_aspect
+
+ def set_bbox_transmuter(self, bbox_transmuter):
+ """
+ Set the transmuter object
+
+ ACCEPTS: BboxTransmuterBase (or its derivatives) instance
+ """
+ self._bbox_transmuter = bbox_transmuter
+
+ def get_bbox_transmuter(self):
+ "Return the current transmuter object"
+ return self._bbox_transmuter
+
+ def get_path(self):
+ """
+ Return the mutated path of the rectangle
+ """
+
+ _path = self.get_bbox_transmuter()(self._x, self._y,
+ self._width, self._height,
+ self.get_mutation_scale(),
+ self.get_mutation_aspect())
+ return _path
+
+
+ # Followong methods are borrowed from the Rectangle class.
+
+ def get_x(self):
+ "Return the left coord of the rectangle"
+ return self._x
+
+ def get_y(self):
+ "Return the bottom coord of the rectangle"
+ return self._y
+
+ def get_width(self):
+ "Return the width of the rectangle"
+ return self._width
+
+ def get_height(self):
+ "Return the height of the rectangle"
+ return self._height
+
+ def set_x(self, x):
+ """
+ Set the left coord of the rectangle
+
+ ACCEPTS: float
+ """
+ self._x = x
+
+ def set_y(self, y):
+ """
+ Set the bottom coord of the rectangle
+
+ ACCEPTS: float
+ """
+ self._y = y
+
+ def set_width(self, w):
+ """
+ Set the width rectangle
+
+ ACCEPTS: float
+ """
+ self._width = w
+
+ def set_height(self, h):
+ """
+ Set the width rectangle
+
+ ACCEPTS: float
+ """
+ self._height = h
+
+ def set_bounds(self, *args):
+ """
+ Set the bounds of the rectangle: l,b,w,h
+
+ ACCEPTS: (left, bottom, width, height)
+ """
+ if len(args)==0:
+ l,b,w,h = args[0]
+ else:
+ l,b,w,h = args
+ self._x = l
+ self._y = b
+ self._width = w
+ self._height = h
+
+
+ def get_bbox(self):
+ return transforms.Bbox.from_bounds(self._x, self._y, self._width,
self._height)
+
Modified: trunk/matplotlib/lib/matplotlib/text.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/text.py 2008-09-17 15:47:02 UTC (rev
6100)
+++ trunk/matplotlib/lib/matplotlib/text.py 2008-09-17 20:52:08 UTC (rev
6101)
@@ -12,7 +12,8 @@
from artist import Artist
from cbook import is_string_like, maxdict
from font_manager import FontProperties
-from patches import bbox_artist, YAArrow
+from patches import bbox_artist, YAArrow, FancyBboxPatch
+import transforms as mtransforms
from transforms import Affine2D, Bbox
from lines import Line2D
@@ -77,6 +78,50 @@
==========================
=========================================================================
"""
+
+
+
+# TODO : This function may move into the Text class as a method. As a
+# matter of fact, The information from the _get_textbox function
+# should be available during the Text._get_layout() call, which is
+# called within the _get_textbox. So, it would better to move this
+# function as a method with some refactoring of _get_layout method.
+
+def _get_textbox(text, renderer):
+ """
+ figure out the bounding box of the text. Unlike get_extents()
+ method, The bbox size of the text before the rotation is
+ calculated.
+ """
+
+ projected_xs = []
+ projected_ys = []
+
+ theta = text.get_rotation()/180.*math.pi
+ tr = mtransforms.Affine2D().rotate(-theta)
+
+ for t, wh, x, y in text._get_layout(renderer)[1]:
+ w, h = wh
+
+
+ xt1, yt1 = tr.transform_point((x, y))
+ xt2, yt2 = xt1+w, yt1+h
+
+ projected_xs.extend([xt1, xt2])
+ projected_ys.extend([yt1, yt2])
+
+
+ xt_box, yt_box = min(projected_xs), min(projected_ys)
+ w_box, h_box = max(projected_xs) - xt_box, max(projected_ys) - yt_box
+
+ tr = mtransforms.Affine2D().rotate(theta)
+
+ x_box, y_box = tr.transform_point((xt_box, yt_box))
+
+ return x_box, y_box, w_box, h_box
+
+
+
class Text(Artist):
"""
Handle storing and drawing of text in window or data coordinates
@@ -121,6 +166,7 @@
self._rotation = rotation
self._fontproperties = fontproperties
self._bbox = None
+ self._bbox_patch = None # a FanceBboxPatch instance
self._renderer = None
if linespacing is None:
linespacing = 1.2 # Maybe use rcParam later.
@@ -271,15 +317,65 @@
def set_bbox(self, rectprops):
"""
- Draw a bounding box around self. rect props are any settable
+ Draw a bounding box around self. rectprops are any settable
properties for a rectangle, eg facecolor='red', alpha=0.5.
t.set_bbox(dict(facecolor='red', alpha=0.5))
- ACCEPTS: rectangle prop dict plus key 'pad' which is a pad in points
+ If rectprops has "boxstyle" key. A FancyBboxPatch
+ is initiallized with rectprops and will be drawn. The mutation
+ scale of the FancyBboxPath is set to the fontsize.
+
+ ACCEPTS: rectangle prop dict plus key 'pad' which is a pad in
+ points. If "boxstyle" key exists, the input dictionary should
+ be a valid input for the FancyBboxPatch class.
+
"""
- self._bbox = rectprops
+ # The self._bbox_patch object is created only if rectprops has
+ # boxstyle key. Otherwise, self._bbox will be set to the
+ # rectprops and the bbox will be drawn using bbox_artist
+ # function. This is to keep the backward compatibility.
+
+ if rectprops is not None and rectprops.has_key("boxstyle"):
+ props = rectprops.copy()
+ boxstyle = props.pop("boxstyle")
+ bbox_transmuter = props.pop("bbox_transmuter", None)
+
+ self._bbox_patch = FancyBboxPatch((0., 0.),
+ 1., 1.,
+ boxstyle=boxstyle,
+ bbox_transmuter=bbox_transmuter,
+
transform=mtransforms.IdentityTransform(),
+ **props)
+ self._bbox = None
+ else:
+ self._bbox_patch = None
+ self._bbox = rectprops
+
+
+ def get_bbox_patch(self):
+ """
+ Retrun the bbox Patch object. Returns None if the the
+ FancyBboxPatch is not made.
+ """
+ return self._bbox_patch
+
+
+ def _draw_bbox(self, renderer, posx, posy):
+ """ Update the location and the size of the bbox, and draw.
+ """
+ x_box, y_box, w_box, h_box = _get_textbox(self, renderer)
+ self._bbox_patch.set_bounds(0., 0.,
+ w_box, h_box)
+ theta = self.get_rotation()/180.*math.pi
+ tr = mtransforms.Affine2D().rotate(theta)
+ tr = tr.translate(posx+x_box, posy+y_box)
+ self._bbox_patch.set_transform(tr)
+ self._bbox_patch.set_mutation_scale(self.get_size())
+ self._bbox_patch.draw(renderer)
+
+
def draw(self, renderer):
#return
if renderer is not None:
@@ -287,6 +383,22 @@
if not self.get_visible(): return
if self._text=='': return
+ bbox, info = self._get_layout(renderer)
+ trans = self.get_transform()
+
+
+ # don't use self.get_position here, which refers to text position
+ # in Text, and dash position in TextWithDash:
+ posx = float(self.convert_xunits(self._x))
+ posy = float(self.convert_yunits(self._y))
+
+ posx, posy = trans.transform_point((posx, posy))
+ canvasw, canvash = renderer.get_canvas_width_height()
+
+ # draw the FancyBboxPatch
+ if self._bbox_patch:
+ self._draw_bbox(renderer, posx, posy)
+
gc = renderer.new_gc()
gc.set_foreground(self._color)
gc.set_alpha(self._alpha)
@@ -297,17 +409,8 @@
bbox_artist(self, renderer, self._bbox)
angle = self.get_rotation()
- bbox, info = self._get_layout(renderer)
- trans = self.get_transform()
- # don't use self.get_position here, which refers to text position
- # in Text, and dash position in TextWithDash:
- posx = float(self.convert_xunits(self._x))
- posy = float(self.convert_yunits(self._y))
- posx, posy = trans.transform_point((posx, posy))
- canvasw, canvash = renderer.get_canvas_width_height()
-
if rcParams['text.usetex']:
for line, wh, x, y in info:
x = x + posx
@@ -401,7 +504,7 @@
return (x, y, self._text, self._color,
self._verticalalignment, self._horizontalalignment,
hash(self._fontproperties), self._rotation,
- self.figure.dpi, id(self._renderer),
+ self.figure.dpi, id(self._renderer),
)
def get_text(self):
This was sent by the SourceForge.net collaborative development platform, the
world's largest Open Source development site.
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins