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
Matplotlib-checkins@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to