|
From: <lee...@us...> - 2008-12-03 07:40:13
|
Revision: 6480
http://matplotlib.svn.sourceforge.net/matplotlib/?rev=6480&view=rev
Author: leejjoon
Date: 2008-12-03 07:40:09 +0000 (Wed, 03 Dec 2008)
Log Message:
-----------
reorganization of style classes in patches.py
Modified Paths:
--------------
trunk/matplotlib/CHANGELOG
trunk/matplotlib/lib/matplotlib/patches.py
Added Paths:
-----------
trunk/matplotlib/examples/pylab_examples/fancyarrow_demo.py
trunk/matplotlib/examples/pylab_examples/fancybox_demo2.py
Modified: trunk/matplotlib/CHANGELOG
===================================================================
--- trunk/matplotlib/CHANGELOG 2008-12-02 22:27:38 UTC (rev 6479)
+++ trunk/matplotlib/CHANGELOG 2008-12-03 07:40:09 UTC (rev 6480)
@@ -1,3 +1,7 @@
+2008-12-02 The transmuter classes in the patches.py are reorganized as
+ subclasses of the Style classes. A few more box and arrow
+ styles are added. -JJL
+
2008-12-02 Fixed a bug in the new legend class that didn't allowed
a tuple of coordinate vlaues as loc. -JJL
Added: trunk/matplotlib/examples/pylab_examples/fancyarrow_demo.py
===================================================================
--- trunk/matplotlib/examples/pylab_examples/fancyarrow_demo.py (rev 0)
+++ trunk/matplotlib/examples/pylab_examples/fancyarrow_demo.py 2008-12-03 07:40:09 UTC (rev 6480)
@@ -0,0 +1,43 @@
+import matplotlib.patches as mpatches
+import matplotlib.pyplot as plt
+
+styles = mpatches.ArrowStyle.get_styles()
+
+figheight = (len(styles)+.5)
+fig1 = plt.figure(1, (4, figheight))
+fontsize = 0.3 * fig1.dpi
+
+ax = plt.Axes(fig1, [0, 0, 1, 1], frameon=False, aspect=1.)
+fig1.add_axes(ax)
+
+ax.set_xlim(0, 4)
+ax.set_ylim(0, figheight)
+
+for i, (stylename, styleclass) in enumerate(sorted(styles.items())):
+ y = (float(len(styles)) -0.25 - i) # /figheight
+ p = mpatches.Circle((3.2, y), 0.2, fc="w")
+ ax.add_patch(p)
+ #ax.plot([0.8], [y], "o", mec="b", mfc="w", ms=20, transform=fig1.transFigure)
+ #ax.scatter([0.8], [y], s=20*20, marker="o", edgecolors=["b"], facecolors=["w"],
+ # )
+ ax.annotate(stylename, (3.2, y),
+ (2., y),
+ #xycoords="figure fraction", textcoords="figure fraction",
+ ha="right", va="center",
+ size=fontsize,
+ arrowprops=dict(arrowstyle=stylename,
+ patchB=p,
+ shrinkA=5,
+ shrinkB=5,
+ fc="w", ec="k",
+ connectionstyle="arc3,rad=-0.05",
+ ),
+ bbox=dict(boxstyle="square", fc="w"))
+
+ax.xaxis.set_visible(False)
+ax.yaxis.set_visible(False)
+
+
+
+plt.draw()
+plt.show()
Added: trunk/matplotlib/examples/pylab_examples/fancybox_demo2.py
===================================================================
--- trunk/matplotlib/examples/pylab_examples/fancybox_demo2.py (rev 0)
+++ trunk/matplotlib/examples/pylab_examples/fancybox_demo2.py 2008-12-03 07:40:09 UTC (rev 6480)
@@ -0,0 +1,17 @@
+import matplotlib.patches as mpatch
+import matplotlib.pyplot as plt
+
+styles = mpatch.BoxStyle.get_styles()
+
+figheight = (len(styles)+.5)
+fig1 = plt.figure(1, (4, figheight))
+fontsize = 0.4 * fig1.dpi
+
+for i, (stylename, styleclass) in enumerate(styles.items()):
+ fig1.text(0.5, (float(len(styles)) - 0.5 - i)/figheight, stylename,
+ ha="center",
+ size=fontsize,
+ transform=fig1.transFigure,
+ bbox=dict(boxstyle=stylename, fc="w", ec="k"))
+plt.draw()
+plt.show()
Modified: trunk/matplotlib/lib/matplotlib/patches.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/patches.py 2008-12-02 22:27:38 UTC (rev 6479)
+++ trunk/matplotlib/lib/matplotlib/patches.py 2008-12-03 07:40:09 UTC (rev 6480)
@@ -289,6 +289,7 @@
rgbFace = (r, g, b)
gc.set_alpha(a)
+
if self._hatch:
gc.set_hatch(self._hatch )
@@ -1366,319 +1367,576 @@
r.draw(renderer)
-class BboxTransmuterBase(object):
+
+def _pprint_table(_table, leadingspace=2):
"""
- :class:`BBoxTransmuterBase` and its derivatives are used to make a
- fancy box around a given rectangle. The :meth:`__call__` method
- returns the :class:`~matplotlib.path.Path` of the fancy box. This
- class is not an artist and actual drawing of the fancy box is done
- by the :class:`FancyBboxPatch` class.
+ Given the list of list of strings, return a string of REST table format.
"""
+ if leadingspace:
+ pad = ' '*leadingspace
+ else:
+ pad = ''
- # 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.
+ columns = [[] for cell in _table[0]]
- def __init__(self):
- super(BboxTransmuterBase, self).__init__()
+ for row in _table:
+ for column, cell in zip(columns, row):
+ column.append(cell)
+
+
+ col_len = [max([len(cell) for cell in column]) for column in columns]
+ lines = []
+ table_formatstr = pad + ' '.join([('=' * cl) for cl in col_len])
+ lines.append('')
+ lines.append(table_formatstr)
+ lines.append(pad + ' '.join([cell.ljust(cl) for cell, cl in zip(_table[0], col_len)]))
+ lines.append(table_formatstr)
+
+ lines.extend([(pad + ' '.join([cell.ljust(cl) for cell, cl in zip(row, col_len)]))
+ for row in _table[1:]])
+
+ lines.append(table_formatstr)
+ lines.append('')
+ return "\n".join(lines)
- def transmute(self, x0, y0, width, height, mutation_size):
+
+def _pprint_styles(_styles, leadingspace=2):
+ """
+ A helper function for the _Style class. Given the dictionary of
+ (stylename : styleclass), return a formatted string listing all the
+ styles. Used to update the documentation.
+ """
+ if leadingspace:
+ pad = ' '*leadingspace
+ else:
+ pad = ''
+
+ names, attrss, clss = [], [], []
+
+ import inspect
+
+ _table = [["Class", "Name", "Attrs"]]
+
+ for name, cls in sorted(_styles.items()):
+ args, varargs, varkw, defaults = inspect.getargspec(cls.__init__)
+ if defaults:
+ args = [(argname, argdefault) \
+ for argname, argdefault in zip(args[1:], defaults)]
+ else:
+ args = []
+
+ _table.append([cls.__name__, name,
+ ",".join([("%s=%s" % (an, av)) for an, av in args])])
+
+ return _pprint_table(_table)
+
+
+
+class _Style(object):
+ """
+ A base class for the Styles. It is meant to be a container class,
+ where actual styles are declared as subclass of it, and it
+ provides some helper functions.
+ """
+ def __new__(self, stylename, **kw):
"""
- The transmute method is a very core of the
- :class:`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 amount of
- padding and etc. will be scaled. It returns a
- :class:`~matplotlib.path.Path` instance.
+ return the instance of the subclass with the given style name.
"""
- raise NotImplementedError('Derived must override')
+ # the "class" should have the _style_list attribute, which is
+ # a dictionary of stylname, style class paie.
+
+ _list = stylename.replace(" ","").split(",")
+ _name = _list[0].lower()
+ try:
+ _cls = self._style_list[_name]
+ except KeyError:
+ raise ValueError("Unknown style : %s" % stylename)
+ try:
+ _args_pair = [cs.split("=") for cs in _list[1:]]
+ _args = dict([(k, float(v)) for k, v in _args_pair])
+ except ValueError:
+ raise ValueError("Incorrect style argument : %s" % stylename)
+ _args.update(kw)
- def __call__(self, x0, y0, width, height, mutation_size,
- aspect_ratio=1.):
+ return _cls(**_args)
+
+
+ @classmethod
+ def get_styles(klass):
"""
- The __call__ method a thin wrapper around the transmute method
- and take care of the aspect.
+ A class method which returns a dictionary of available styles.
"""
- 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)
+ return klass._style_list
+ @classmethod
+ def pprint_styles(klass):
+ """
+ A class method which returns a string of the available styles.
+ """
+ return _pprint_styles(klass._style_list)
-class SquareBoxTransmuter(BboxTransmuterBase):
+
+
+class BoxStyle(_Style):
"""
- Simple square box.
+ :class:`BoxStyle` is a container class which defines several
+ boxstyle classes, which are used for :class:`FancyBoxPatch`.
- *pad*: an amount of padding.
+ A style object can be created as
+
+ BoxStyle.Round(pad=0.2)
+
+ or
+
+ BoxStyle("Round", pad=0.2)
+
+ or
+
+ BoxStyle("Round, pad=0.2")
+
+ Following boxstyle classes are defined.
+
+ %(AvailableBoxstyles)s
+
+ An instance of any boxstyle class is an callable object,
+ whose call signature is
+
+ __call__(self, x0, y0, width, height, mutation_size, aspect_ratio=1.)
+
+ and returns a :class:`Path` instance. *x0*, *y0*, *width* and
+ *height* specify the location and size of the box to be
+ drawn. *mutation_scale* determines the overall size of the
+ mutation (by which I mean the transformation of the rectangle to
+ the fancy box). *mutation_aspect* determines the aspect-ratio of
+ the mutation.
+
"""
+
+ _style_list = {}
- def __init__(self, pad=0.3):
- self.pad = pad
- super(SquareBoxTransmuter, self).__init__()
- def transmute(self, x0, y0, width, height, mutation_size):
+ class _Base(object):
+ """
+ :class:`BBoxTransmuterBase` and its derivatives are used to make a
+ fancy box around a given rectangle. The :meth:`__call__` method
+ returns the :class:`~matplotlib.path.Path` of the fancy box. This
+ class is not an artist and actual drawing of the fancy box is done
+ by the :class:`FancyBboxPatch` class.
+ """
- # padding
- pad = mutation_size * self.pad
+ # 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.
- # width and height with padding added.
- width, height = width + 2.*pad, \
- height + 2.*pad,
+ def __init__(self):
+ """
+ initializtion.
+ """
+ super(BoxStyle._Base, self).__init__()
- # 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)
+ def transmute(self, x0, y0, width, height, mutation_size):
+ """
+ The transmute method is a very core of the
+ :class:`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 amount of
+ padding and etc. will be scaled. It returns a
+ :class:`~matplotlib.path.Path` instance.
+ """
+ raise NotImplementedError('Derived must override')
- return path
-class RoundBoxTransmuter(BboxTransmuterBase):
- """
- A box with round corners.
- """
+ def __call__(self, x0, y0, width, height, mutation_size,
+ aspect_ratio=1.):
+ """
+ Given the location and size of the box, return the path of
+ the box around it.
- def __init__(self, pad=0.3, rounding_size=None):
- self.pad = pad
- self.rounding_size = rounding_size
- BboxTransmuterBase.__init__(self)
+ - *x0*, *y0*, *width*, *height* : location and size of the box
+ - *mutation_size* : a reference scale for the mutation.
+ - *aspect_ratio* : aspect-ration for the mutation.
+ """
+ # The __call__ method is a thin wrapper around the transmute method
+ # and take care of the aspect.
- def transmute(self, x0, y0, width, height, mutation_size):
+ 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)
- # 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,
+ class Square(_Base):
+ """
+ A simple square box.
+ """
+ def __init__(self, pad=0.3):
+ """
+ *pad*
+ amount of padding
+ """
+
+ self.pad = pad
+ super(BoxStyle.Square, self).__init__()
- x0, y0 = x0-pad, y0-pad,
- x1, y1 = x0+width, y0 + height
+ def transmute(self, x0, y0, width, height, mutation_size):
- # 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)]
+ # padding
+ pad = mutation_size * self.pad
- 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]
+ # width and height with padding added.
+ width, height = width + 2.*pad, \
+ height + 2.*pad,
- path = Path(cp, com)
+ # boundary of the padded box
+ x0, y0 = x0-pad, y0-pad,
+ x1, y1 = x0+width, y0 + height
- return path
+ 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]
-class Round4BoxTransmuter(BboxTransmuterBase):
- """
- A box with round edges.
- """
+ path = Path(cp, com)
- def __init__(self, pad=0.3, rounding_size=None):
- self.pad = pad
- self.rounding_size = rounding_size
- BboxTransmuterBase.__init__(self)
+ return path
- def transmute(self, x0, y0, width, height, mutation_size):
+ _style_list["square"] = Square
- # padding
- pad = mutation_size * self.pad
- # roudning size. Use a half of the pad if not set.
- if self.rounding_size:
- dr = mutation_size * self.rounding_size
- else:
- dr = pad / 2.
+ class LArrow(_Base):
+ """
+ (left) Arrow Box
+ """
- width, height = width + 2.*pad - 2*dr, \
- height + 2.*pad - 2*dr,
+ def __init__(self, pad=0.3):
+ self.pad = pad
+ super(BoxStyle.LArrow, self).__init__()
+ def transmute(self, x0, y0, width, height, mutation_size):
- x0, y0 = x0-pad+dr, y0-pad+dr,
- x1, y1 = x0+width, y0 + height
+ # padding
+ pad = mutation_size * self.pad
+ # width and height with padding added.
+ width, height = width + 2.*pad, \
+ height + 2.*pad,
- cp = [(x0, y0),
- (x0+dr, y0-dr), (x1-dr, y0-dr), (x1, y0),
- (x1+dr, y0+dr), (x1+dr, y1-dr), (x1, y1),
- (x1-dr, y1+dr), (x0+dr, y1+dr), (x0, y1),
- (x0-dr, y1-dr), (x0-dr, y0+dr), (x0, y0),
- (x0, y0)]
+ # boundary of the padded box
+ x0, y0 = x0-pad, y0-pad,
+ x1, y1 = x0+width, y0 + height
- com = [Path.MOVETO,
- Path.CURVE4, Path.CURVE4, Path.CURVE4,
- Path.CURVE4, Path.CURVE4, Path.CURVE4,
- Path.CURVE4, Path.CURVE4, Path.CURVE4,
- Path.CURVE4, Path.CURVE4, Path.CURVE4,
- Path.CLOSEPOLY]
+ dx = (y1-y0)/2.
+ dxx = dx*.5
+ # adjust x0. 1.4 <- sqrt(2)
+ x0 = x0 + pad / 1.4
+
+ cp = [(x0+dxx, y0), (x1, y0), (x1, y1), (x0+dxx, y1),
+ (x0+dxx, y1+dxx), (x0-dx, y0+dx), (x0+dxx, y0-dxx), # arrow
+ (x0+dxx, y0), (x0+dxx, y0)]
- path = Path(cp, com)
+ com = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO,
+ Path.LINETO, Path.LINETO, Path.LINETO,
+ Path.LINETO, Path.CLOSEPOLY]
- return path
+ path = Path(cp, com)
+ return path
+ _style_list["larrow"] = LArrow
-class SawtoothBoxTransmuter(BboxTransmuterBase):
- """
- A sawtooth box.
- """
+ class RArrow(LArrow):
+ """
+ (right) Arrow Box
+ """
- def __init__(self, pad=0.3, tooth_size=None):
- self.pad = pad
- self.tooth_size = tooth_size
- BboxTransmuterBase.__init__(self)
+ def __init__(self, pad=0.3):
+ self.pad = pad
+ super(BoxStyle.RArrow, self).__init__()
- def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size):
+ def transmute(self, x0, y0, width, height, mutation_size):
+ p = BoxStyle.LArrow.transmute(self, x0, y0,
+ width, height, mutation_size)
- # padding
- pad = mutation_size * self.pad
+ p.vertices[:,0] = 2*x0 + width - p.vertices[:,0]
- # size of sawtooth
- if self.tooth_size is None:
- tooth_size = self.pad * .5 * mutation_size
- else:
- tooth_size = self.tooth_size * mutation_size
+ return p
- tooth_size2 = tooth_size / 2.
- width, height = width + 2.*pad - tooth_size, \
- height + 2.*pad - tooth_size,
+
+ _style_list["rarrow"] = RArrow
- # the sizes of the vertical and horizontal sawtooth are
- # separately adjusted to fit the given box size.
- dsx_n = int(round((width - tooth_size) / (tooth_size * 2))) * 2
- dsx = (width - tooth_size) / dsx_n
- dsy_n = int(round((height - tooth_size) / (tooth_size * 2))) * 2
- dsy = (height - tooth_size) / dsy_n
+ class Round(_Base):
+ """
+ A box with round corners.
+ """
- x0, y0 = x0-pad+tooth_size2, y0-pad+tooth_size2
- x1, y1 = x0+width, y0 + height
+ def __init__(self, pad=0.3, rounding_size=None):
+ """
+ *pad*
+ amount of padding
+ *rounding_size*
+ rounding radius of corners. *pad* if None
+ """
+ self.pad = pad
+ self.rounding_size = rounding_size
+ super(BoxStyle.Round, self).__init__()
- bottom_saw_x = [x0] + \
- [x0 + tooth_size2 + dsx*.5* i for i in range(dsx_n*2)] + \
- [x1 - tooth_size2]
- bottom_saw_y = [y0] + \
- [y0 - tooth_size2, y0, y0 + tooth_size2, y0] * dsx_n + \
- [y0 - tooth_size2]
+ def transmute(self, x0, y0, width, height, mutation_size):
- right_saw_x = [x1] + \
- [x1 + tooth_size2, x1, x1 - tooth_size2, x1] * dsx_n + \
- [x1 + tooth_size2]
- right_saw_y = [y0] + \
- [y0 + tooth_size2 + dsy*.5* i for i in range(dsy_n*2)] + \
- [y1 - tooth_size2]
+ # padding
+ pad = mutation_size * self.pad
- top_saw_x = [x1] + \
- [x1 - tooth_size2 - dsx*.5* i for i in range(dsx_n*2)] + \
- [x0 + tooth_size2]
- top_saw_y = [y1] + \
- [y1 + tooth_size2, y1, y1 - tooth_size2, y1] * dsx_n + \
- [y1 + tooth_size2]
+ # size of the roudning corner
+ if self.rounding_size:
+ dr = mutation_size * self.rounding_size
+ else:
+ dr = pad
- left_saw_x = [x0] + \
- [x0 - tooth_size2, x0, x0 + tooth_size2, x0] * dsy_n + \
- [x0 - tooth_size2]
- left_saw_y = [y1] + \
- [y1 - tooth_size2 - dsy*.5* i for i in range(dsy_n*2)] + \
- [y0 + tooth_size2]
+ width, height = width + 2.*pad, \
+ height + 2.*pad,
- saw_vertices = zip(bottom_saw_x, bottom_saw_y) + \
- zip(right_saw_x, right_saw_y) + \
- zip(top_saw_x, top_saw_y) + \
- zip(left_saw_x, left_saw_y) + \
- [(bottom_saw_x[0], bottom_saw_y[0])]
- return saw_vertices
+ 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)]
- def transmute(self, x0, y0, width, height, mutation_size):
+ 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]
- saw_vertices = self._get_sawtooth_vertices(x0, y0, width, height, mutation_size)
- path = Path(saw_vertices)
- return path
+ path = Path(cp, com)
+ return path
-class RoundtoothBoxTransmuter(SawtoothBoxTransmuter):
- """
- A roundtooth(?) box.
- """
+ _style_list["round"] = Round
- def transmute(self, x0, y0, width, height, mutation_size):
- saw_vertices = self._get_sawtooth_vertices(x0, y0, width, height, mutation_size)
+ class Round4(_Base):
+ """
+ Another box with round edges.
+ """
- cp = [Path.MOVETO] + ([Path.CURVE3, Path.CURVE3] * ((len(saw_vertices)-1)//2))
- path = Path(saw_vertices, cp)
+ def __init__(self, pad=0.3, rounding_size=None):
+ """
+ *pad*
+ amount of padding
- return path
+ *rounding_size*
+ rounding size of edges. *pad* if None
+ """
+
+ self.pad = pad
+ self.rounding_size = rounding_size
+ super(BoxStyle.Round4, self).__init__()
+ def transmute(self, x0, y0, width, height, mutation_size):
-def _list_available_boxstyles(transmuters):
- """ a helper function of the :class:`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
+ # padding
+ pad = mutation_size * self.pad
+ # roudning size. Use a half of the pad if not set.
+ if self.rounding_size:
+ dr = mutation_size * self.rounding_size
+ else:
+ dr = pad / 2.
+ width, height = width + 2.*pad - 2*dr, \
+ height + 2.*pad - 2*dr,
+ x0, y0 = x0-pad+dr, y0-pad+dr,
+ x1, y1 = x0+width, y0 + height
+
+ cp = [(x0, y0),
+ (x0+dr, y0-dr), (x1-dr, y0-dr), (x1, y0),
+ (x1+dr, y0+dr), (x1+dr, y1-dr), (x1, y1),
+ (x1-dr, y1+dr), (x0+dr, y1+dr), (x0, y1),
+ (x0-dr, y1-dr), (x0-dr, y0+dr), (x0, y0),
+ (x0, y0)]
+
+ com = [Path.MOVETO,
+ Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.CURVE4, Path.CURVE4, Path.CURVE4,
+ Path.CLOSEPOLY]
+
+ path = Path(cp, com)
+
+ return path
+
+ _style_list["round4"] = Round4
+
+
+ class Sawtooth(_Base):
+ """
+ A sawtooth box.
+ """
+
+ def __init__(self, pad=0.3, tooth_size=None):
+ """
+ *pad*
+ amount of padding
+
+ *tooth_size*
+ size of the sawtooth. pad* if None
+ """
+ self.pad = pad
+ self.tooth_size = tooth_size
+ super(BoxStyle.Sawtooth, self).__init__()
+
+ def _get_sawtooth_vertices(self, x0, y0, width, height, mutation_size):
+
+
+ # padding
+ pad = mutation_size * self.pad
+
+ # size of sawtooth
+ if self.tooth_size is None:
+ tooth_size = self.pad * .5 * mutation_size
+ else:
+ tooth_size = self.tooth_size * mutation_size
+
+ tooth_size2 = tooth_size / 2.
+ width, height = width + 2.*pad - tooth_size, \
+ height + 2.*pad - tooth_size,
+
+ # the sizes of the vertical and horizontal sawtooth are
+ # separately adjusted to fit the given box size.
+ dsx_n = int(round((width - tooth_size) / (tooth_size * 2))) * 2
+ dsx = (width - tooth_size) / dsx_n
+ dsy_n = int(round((height - tooth_size) / (tooth_size * 2))) * 2
+ dsy = (height - tooth_size) / dsy_n
+
+
+ x0, y0 = x0-pad+tooth_size2, y0-pad+tooth_size2
+ x1, y1 = x0+width, y0 + height
+
+
+ bottom_saw_x = [x0] + \
+ [x0 + tooth_size2 + dsx*.5* i for i in range(dsx_n*2)] + \
+ [x1 - tooth_size2]
+ bottom_saw_y = [y0] + \
+ [y0 - tooth_size2, y0, y0 + tooth_size2, y0] * dsx_n + \
+ [y0 - tooth_size2]
+
+ right_saw_x = [x1] + \
+ [x1 + tooth_size2, x1, x1 - tooth_size2, x1] * dsx_n + \
+ [x1 + tooth_size2]
+ right_saw_y = [y0] + \
+ [y0 + tooth_size2 + dsy*.5* i for i in range(dsy_n*2)] + \
+ [y1 - tooth_size2]
+
+ top_saw_x = [x1] + \
+ [x1 - tooth_size2 - dsx*.5* i for i in range(dsx_n*2)] + \
+ [x0 + tooth_size2]
+ top_saw_y = [y1] + \
+ [y1 + tooth_size2, y1, y1 - tooth_size2, y1] * dsx_n + \
+ [y1 + tooth_size2]
+
+ left_saw_x = [x0] + \
+ [x0 - tooth_size2, x0, x0 + tooth_size2, x0] * dsy_n + \
+ [x0 - tooth_size2]
+ left_saw_y = [y1] + \
+ [y1 - tooth_size2 - dsy*.5* i for i in range(dsy_n*2)] + \
+ [y0 + tooth_size2]
+
+ saw_vertices = zip(bottom_saw_x, bottom_saw_y) + \
+ zip(right_saw_x, right_saw_y) + \
+ zip(top_saw_x, top_saw_y) + \
+ zip(left_saw_x, left_saw_y) + \
+ [(bottom_saw_x[0], bottom_saw_y[0])]
+
+ return saw_vertices
+
+
+ def transmute(self, x0, y0, width, height, mutation_size):
+
+ saw_vertices = self._get_sawtooth_vertices(x0, y0, width, height, mutation_size)
+ path = Path(saw_vertices)
+ return path
+
+ _style_list["sawtooth"] = Sawtooth
+
+
+ class Roundtooth(Sawtooth):
+ """
+ A roundtooth(?) box.
+ """
+
+ def __init__(self, pad=0.3, tooth_size=None):
+ """
+ *pad*
+ amount of padding
+
+ *tooth_size*
+ size of the sawtooth. pad* if None
+ """
+ super(BoxStyle.Roundtooth, self).__init__(pad, tooth_size)
+
+
+ def transmute(self, x0, y0, width, height, mutation_size):
+
+ saw_vertices = self._get_sawtooth_vertices(x0, y0, width, height, mutation_size)
+
+ cp = [Path.MOVETO] + ([Path.CURVE3, Path.CURVE3] * ((len(saw_vertices)-1)//2))
+ path = Path(saw_vertices, cp)
+
+ return path
+
+ _style_list["roundtooth"] = Roundtooth
+
+ __doc__ = cbook.dedent(__doc__) % \
+ {"AvailableBoxstyles": _pprint_styles(_style_list)}
+
+
class FancyBboxPatch(Patch):
"""
Draw a fancy box around a rectangle with lower left at *xy*=(*x*,
@@ -1689,27 +1947,8 @@
transformation of the rectangle box to the fancy box is delegated
to the :class:`BoxTransmuterBase` and its derived classes.
- *boxstyle* determines what kind of fancy box will be drawn. In
- other words, it selects the :class:`BboxTransmuter` class to use,
- and sets optional attributes.
-
- *bbox_transmuter* can specify a custom :class:`BboxTransmuter`
- instance.
-
- *mutation_scale* determines the overall size of the mutation (by
- which I mean the transformation of the rectangle to the fancy
- path)
-
- *mutation_aspect* determines the aspect-ratio of the mutation.
"""
- _fancy_bbox_transmuters = {"square":SquareBoxTransmuter,
- "round":RoundBoxTransmuter,
- "round4":Round4BoxTransmuter,
- "sawtooth":SawtoothBoxTransmuter,
- "roundtooth":RoundtoothBoxTransmuter,
- }
-
def __str__(self):
return self.__class__.__name__ \
+ "FancyBboxPatch(%g,%g;%gx%g)" % (self._x, self._y, self._width, self._height)
@@ -1725,18 +1964,13 @@
*width*, *height*
- *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.
+ *boxstyle* determines what kind of fancy box will be drawn. It
+ can be a string of the style name with a comma separated
+ attribute, or an instance of :class:`BoxStyle`. Following box
+ styles are available.
%(AvailableBoxstyles)s
- The *boxstyle* name can be "custom", in which case the
- *bbox_transmuter* argument needs to be set, which should be an
- instance of :class:`BboxTransmuterBase` (or its derived).
-
*mutation_scale* : a value with which attributes of boxstyle
(e.g., pad) will be scaled. default=1.
@@ -1767,17 +2001,12 @@
kwdoc = dict()
- kwdoc["AvailableBoxstyles"]="\n".join([" - " + l \
- for l in _list_available_boxstyles(_fancy_bbox_transmuters)])
+ kwdoc["AvailableBoxstyles"]=_pprint_styles(BoxStyle._style_list)
kwdoc.update(artist.kwdocd)
__init__.__doc__ = cbook.dedent(__init__.__doc__) % kwdoc
del kwdoc
- @classmethod
- def list_available_boxstyles(cls):
- return _list_available_boxstyles(cls._fancy_bbox_transmuters)
-
def set_boxstyle(self, boxstyle=None, **kw):
"""
Set the box style.
@@ -1799,29 +2028,20 @@
if boxstyle==None:
# print out available boxstyles and return.
- print " Following box styles are available."
- for l in self.list_available_boxstyles():
- print " - " + l
+ print "Following box styles are available:"
+ print BoxStyle.pprint_styles()
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)
+ if isinstance(boxstyle, BoxStyle._Base):
+ self._bbox_transmuter = boxstyle
+ elif callable(boxstyle):
+ self._bbox_transmuter = boxstyle
+ else:
+ self._bbox_transmuter = BoxStyle(boxstyle, **kw)
+
- boxstyle_args.update(kw)
- self._bbox_transmuter = bbox_transmuter_cls(**boxstyle_args)
kwdoc = dict()
- kwdoc["AvailableBoxstyles"]=" | ".join([l \
- for l in _list_available_boxstyles(_fancy_bbox_transmuters)])
+ kwdoc["AvailableBoxstyles"]=_pprint_styles(BoxStyle._style_list)
kwdoc.update(artist.kwdocd)
set_boxstyle.__doc__ = cbook.dedent(set_boxstyle.__doc__) % kwdoc
del kwdoc
@@ -1854,16 +2074,8 @@
"""
return self._mutation_aspect
- def set_bbox_transmuter(self, bbox_transmuter):
- """
- Set the transmuter object
-
- ACCEPTS: :class:`BboxTransmuterBase` (or its derivatives) instance
- """
- self._bbox_transmuter = bbox_transmuter
-
- def get_bbox_transmuter(self):
- "Return the current transmuter object"
+ def get_boxstyle(self):
+ "Return the boxstyle object"
return self._bbox_transmuter
def get_path(self):
@@ -1871,10 +2083,10 @@
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())
+ _path = self.get_boxstyle()(self._x, self._y,
+ self._width, self._height,
+ self.get_mutation_scale(),
+ self.get_mutation_aspect())
return _path
@@ -1954,239 +2166,354 @@
from matplotlib.bezier import split_bezier_intersecting_with_closedpath
from matplotlib.bezier import get_intersection, inside_circle, get_parallels
from matplotlib.bezier import make_wedged_bezier2
-from matplotlib.bezier import split_path_inout, inside_circle
+from matplotlib.bezier import split_path_inout, inside_circle, get_cos_sin
-class ConnectorBase(object):
- """ The ConnectorClass is used to define a path between a two
- points. This class is used in the FancyArrowPatch class. It
- creates a path between point A and point B. When optional patch
- objects (pathcA & patchB) are provided and if they enclose the
- point A or B, the path is clipped to the boundary of the each
- patch. Additionally the path can be shirnked by a fixed size
- (given in points) with shrinkA and shrinkB.
+
+class ConnectionStyle(_Style):
"""
+ :class:`ConnectionStyle` is a container class which defines
+ several connectionstyle classes, which is used to create a path
+ between two points. These are mainly used with
+ :class:`FancyArrowPatch`.
- class SimpleEvent:
- def __init__(self, xy):
- self.x, self.y = xy
+ A connectionstyle object can be either created as
- def _clip(self, path, patchA, patchB):
- """ Clip the path to the boundary of the patchA and patchB.
- The starting point of the path needed to be inside of the
- patchA and the end point inside the patch B. The contains
- methods of each patch object is utilized to test if the point
- is inside the path.
+ ConnectionStyle.Arc3(rad=0.2)
+
+ or
+
+ ConnectionStyle("Arc3", rad=0.2)
+
+ or
+
+ ConnectionStyle("Arc3, rad=0.2")
+
+ Following classes are defined
+
+ %(AvailableConnectorstyles)s
+
+
+ An instance of any connection style class is an callable object,
+ whose call signature is
+
+ __call__(self, posA, posB, patchA=None, patchB=None, shrinkA=2., shrinkB=2.)
+
+ and it returns a :class:`Path` instance. *posA* and *posB are tuples
+ of x,y coordinates of the two points to be connected. *patchA* (or
+ $patchB*) is given, the returned path is clipped so that it start
+ (or end) from the boundary of the patch. The path is further
+ shrinked by *shrinkA* (or *shrinkB*) which is given in points.
+ """
+
+ _style_list = {}
+
+
+ class _Base(object):
"""
+ A base class for connectionstyle classes. The dervided needs
+ to implement a *connect* methods whose call signature is
- if patchA:
- def insideA(xy_display):
- #xy_display = patchA.get_data_transform().transform_point(xy_data)
- xy_event = ConnectorBase.SimpleEvent(xy_display)
- return patchA.contains(xy_event)[0]
+ connect(posA, posB)
+
+ where posA and posB are tuples of x, y coordinates to be
+ connected. The methods needs to return a path connecting two
+ points. This base class defines a __call__ method, and few
+ helper methods.
+ """
- try:
+ class SimpleEvent:
+ def __init__(self, xy):
+ self.x, self.y = xy
+
+ def _clip(self, path, patchA, patchB):
+ """
+ Clip the path to the boundary of the patchA and patchB.
+ The starting point of the path needed to be inside of the
+ patchA and the end point inside the patch B. The *contains*
+ methods of each patch object is utilized to test if the point
+ is inside the path.
+ """
+
+ if patchA:
+ def insideA(xy_display):
+ #xy_display = patchA.get_data_transform().transform_point(xy_data)
+ xy_event = ConnectionStyle._Base.SimpleEvent(xy_display)
+ return patchA.contains(xy_event)[0]
+
+ try:
+ left, right = split_path_inout(path, insideA)
+ except ValueError:
+ right = path
+
+ path = right
+
+ if patchB:
+ def insideB(xy_display):
+ #xy_display = patchB.get_data_transform().transform_point(xy_data)
+ xy_event = ConnectionStyle._Base.SimpleEvent(xy_display)
+ return patchB.contains(xy_event)[0]
+
+ try:
+ left, right = split_path_inout(path, insideB)
+ except ValueError:
+ left = path
+
+ path = left
+
+ return path
+
+
+ def _shrink(self, path, shrinkA, shrinkB):
+ """
+ Shrink the path by fixed size (in points) with shrinkA and shrinkB
+ """
+ if shrinkA:
+ x, y = path.vertices[0]
+ insideA = inside_circle(x, y, shrinkA)
+
left, right = split_path_inout(path, insideA)
- except ValueError:
- right = path
+ path = right
- path = right
+ if shrinkB:
+ x, y = path.vertices[-1]
+ insideB = inside_circle(x, y, shrinkB)
- if patchB:
- def insideB(xy_display):
- #xy_display = patchB.get_data_transform().transform_point(xy_data)
- xy_event = ConnectorBase.SimpleEvent(xy_display)
- return patchB.contains(xy_event)[0]
-
- try:
left, right = split_path_inout(path, insideB)
- except ValueError:
- left = path
+ path = left
- path = left
+ return path
- #ppp = patchB.get_patch_transform().transform_path(patchB.get_path())
- #def insideB(xy_data):
- # return ppp.contains_point(xy_data)
- ##return patchB.contains(ConnectorBase.SimpleEvent(xy))[0]
+ def __call__(self, posA, posB,
+ shrinkA=2., shrinkB=2., patchA=None, patchB=None):
+ """
+ Calls the *connect* method to create a path between *posA*
+ and *posB*. The path is clipped and shrinked.
+ """
+
+ path = self.connect(posA, posB)
- return path
+ clipped_path = self._clip(path, patchA, patchB)
+ shrinked_path = self._shrink(clipped_path, shrinkA, shrinkB)
+ return shrinked_path
- def _shrink(self, path, shrinkA, shrinkB):
+
+ class Arc3(_Base):
"""
- Shrink the path by fixed size (in points) with shrinkA and shrinkB
+ Creates a simple quadratic bezier curve between two
+ points. The curve is created so that the middle contol points
+ (C1) is located at the same distance from the start (C0) and
+ end points(C2) and the distance of the C1 to the line
+ connecting C0-C2 is *rad* times the distance of C0-C2.
"""
- if shrinkA:
- x, y = path.vertices[0]
- insideA = inside_circle(x, y, shrinkA)
- left, right = split_path_inout(path, insideA)
- path = right
+ def __init__(self, rad=0.):
+ """
+ *rad*
+ curvature of the curve.
+ """
+ self.rad = rad
- if shrinkB:
- x, y = path.vertices[-1]
- insideB = inside_circle(x, y, shrinkB)
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x2, y2 = posB
+ x12, y12 = (x1 + x2)/2., (y1 + y2)/2.
+ dx, dy = x2 - x1, y2 - y1
- left, right = split_path_inout(path, insideB)
- path = left
+ f = self.rad
- return path
+ cx, cy = x12 + f*dy, y12 - f*dx
- def __call__(self, posA, posB,
- shrinkA=2., shrinkB=2., patchA=None, patchB=None):
+ vertices = [(x1, y1),
+ (cx, cy),
+ (x2, y2)]
+ codes = [Path.MOVETO,
+ Path.CURVE3,
+ Path.CURVE3]
- path = self.connect(posA, posB)
+ return Path(vertices, codes)
- clipped_path = self._clip(path, patchA, patchB)
- shrinked_path = self._shrink(clipped_path, shrinkA, shrinkB)
+ _style_list["arc3"] = Arc3
+
- return shrinked_path
+ class Angle3(_Base):
+ """
+ Creates a simple quadratic bezier curve between two
+ points. The middle control points is placed at the
+ intersecting point of two lines which crosses the start (or
+ end) point and has a angle of angleA (or angleB).
+ """
-class Arc3Connector(ConnectorBase):
- """ Creates a simple quadratic bezier curve between two
- points. The curve is created so that the middle contol points (C1)
- is located at the same distance from the start (C0) and end
- points(C2) and the distance of the C1 to the line connecting C0-C2
- is *rad* times the distance of C0-C2.
- """
- def __init__(self, rad=0.):
- self.rad = rad
+ def __init__(self, angleA=90, angleB=0):
+ """
+ *angleA*
+ starting angle of the path
- def connect(self, posA, posB):
- x1, y1 = posA
- x2, y2 = posB
- x12, y12 = (x1 + x2)/2., (y1 + y2)/2.
- dx, dy = x2 - x1, y2 - y1
+ *angleB*
+ ending angle of the path
+ """
- f = self.rad
+ self.angleA = angleA
+ self.angleB = angleB
- cx, cy = x12 + f*dy, y12 - f*dx
- vertices = [(x1, y1),
- (cx, cy),
- (x2, y2)]
- codes = [Path.MOVETO,
- Path.CURVE3,
- Path.CURVE3]
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x2, y2 = posB
- return Path(vertices, codes)
+ cosA, sinA = math.cos(self.angleA/180.*math.pi),\
+ math.sin(self.angleA/180.*math.pi),
+ cosB, sinB = math.cos(self.angleB/180.*math.pi),\
+ math.sin(self.angleB/180.*math.pi),
+ cx, cy = get_intersection(x1, y1, cosA, sinA,
+ x2, y2, cosB, sinB)
-class Angle3Connector(ConnectorBase):
- """ Creates a simple quadratic bezier curve between two
- points. The middle control points is placed at the intersecting
- point of two lines which crosses the start (or end) point
- and has a angle of angleA (or angleB).
- """
- def __init__(self, angleA=90, angleB=0):
- self.angleA = angleA
- self.angleB = angleB
+ vertices = [(x1, y1), (cx, cy), (x2, y2)]
+ codes = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
- def connect(self, posA, posB):
- x1, y1 = posA
- x2, y2 = posB
+ return Path(vertices, codes)
- cosA, sinA = math.cos(self.angleA/180.*math.pi),\
- math.sin(self.angleA/180.*math.pi),
- cosB, sinB = math.cos(self.angleB/180.*math.pi),\
- math.sin(self.angleB/180.*math.pi),
+ _style_list["angle3"] = Angle3
- cx, cy = get_intersection(x1, y1, cosA, sinA,
- x2, y2, cosB, sinB)
- vertices = [(x1, y1), (cx, cy), (x2, y2)]
- codes = [Path.MOVETO, Path.CURVE3, Path.CURVE3]
+ class Angle(_Base):
+ """
+ Creates a picewise continuous quadratic bezier path between
+ two points. The path has a one passing-through point placed at
+ the intersecting point of two lines which crosses the start
+ (or end) point and has a angle of angleA (or angleB). The
+ connecting edges are rounded with *rad*.
+ """
- return Path(vertices, codes)
+ def __init__(self, angleA=90, angleB=0, rad=0.):
+ """
+ *angleA*
+ starting angle of the path
+ *angleB*
+ ending angle of the path
-class AngleConnector(ConnectorBase):
- """ Creates a picewise continuous quadratic bezier path between
- two points. The path has a one passing-through point placed at the
- intersecting point of two lines which crosses the start (or end)
- point and has a angle of angleA (or angleB). The connecting edges are
- rounded with *rad*.
- """
+ *rad*
+ rounding radius of the edge
+ """
- def __init__(self, angleA=90, angleB=0, rad=0.):
- self.angleA = angleA
- self.angleB = angleB
+ self.angleA = angleA
+ self.angleB = angleB
- self.rad = rad
+ self.rad = rad
- def connect(self, posA, posB):
- x1, y1 = posA
- x2, y2 = posB
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x2, y2 = posB
- cosA, sinA = math.cos(self.angleA/180.*math.pi),\
- math.sin(self.angleA/180.*math.pi),
- cosB, sinB = math.cos(self.angleB/180.*math.pi),\
- -math.sin(self.angleB/180.*math.pi),
+ cosA, sinA = math.cos(self.angleA/180.*math.pi),\
+ math.sin(self.angleA/180.*math.pi),
+ cosB, sinB = math.cos(self.angleB/180.*math.pi),\
+ -math.sin(self.angleB/180.*math.pi),
- cx, cy = get_intersection(x1, y1, cosA, sinA,
- x2, y2, cosB, sinB)
+ cx, cy = get_intersection(x1, y1, cosA, sinA,
+ x2, y2, cosB, sinB)
- vertices = [(x1, y1)]
- codes = [Path.MOVETO]
+ vertices = [(x1, y1)]
+ codes = [Path.MOVETO]
- if self.rad == 0.:
- vertices.append((cx, cy))
+ if self.rad == 0.:
+ vertices.append((cx, cy))
+ codes.append(Path.LINETO)
+ else:
+ vertices.extend([(cx - self.rad * cosA, cy - self.rad * sinA),
+ (cx, cy),
+ (cx + self.rad * cosB, cy + self.rad * sinB)])
+ codes.extend([Path.LINETO, Path.CURVE3, Path.CURVE3])
+
+ vertices.append((x2, y2))
codes.append(Path.LINETO)
- else:
- vertices.extend([(cx - self.rad * cosA, cy - self.rad * sinA),
- (cx, cy),
- (cx + self.rad * cosB, cy + self.rad * sinB)])
- codes.extend([Path.LINETO, Path.CURVE3, Path.CURVE3])
- vertices.append((x2, y2))
- codes.append(Path.LINETO)
+ return Path(vertices, codes)
- return Path(vertices, codes)
+ _style_list["angle"] = Angle
+ class Arc(_Base):
+ """
+ Creates a picewise continuous quadratic bezier path between
+ two points. The path can have two passing-through points, a
+ point placed at the distance of armA and angle of angleA from
+ point A, another point with respect to point B. The edges are
+ rounded with *rad*.
+ """
-class ArcConnector(ConnectorBase):
- """ Creates a picewise continuous quadratic bezier path between
- two points. The path can have two passing-through points, a point
- placed at the distance of armA and angle of angleA from point A,
- another point with respect to point B. The edges are rounded with
- *rad*.
- """
+ def __init__(self, angleA=0, angleB=0, armA=None, armB=None, rad=0.):
+ """
+ *angleA* :
+ starting angle of the path
- def __init__(self, angleA=0, angleB=0, armA=None, armB=None, rad=0.):
- self.angleA = angleA
- self.angleB = angleB
- self.armA = armA
- self.armB = armB
+ *angleB* :
+ ending angle of the path
- self.rad = rad
+ *armA* :
+ length of the starting arm
- def connect(self, posA, posB):
- x1, y1 = posA
- x2, y2 = posB
+ *armB* :
+ length of the ending arm
- vertices = [(x1, y1)]
- rounded = []
- codes = [Path.MOVETO]
+ *rad* :
+ rounding radius of the edges
+ """
- if self.armA:
- cosA = math.cos(self.angleA/180.*math.pi)
- sinA = math.sin(self.angleA/180.*math.pi)
- #x_armA, y_armB
- d = self.armA - self.rad
- rounded.append((x1 + d*cosA, y1 + d*sinA))
- d = self.armA
- rounded.append((x1 + d*cosA, y1 + d*sinA))
+ self.angleA = angleA
+ self.angleB = angleB
+ self.armA = armA
+ self.armB = armB
- if self.armB:
- cosB = math.cos(self.angleB/180.*math.pi)
- sinB = math.sin(self.angleB/180.*math.pi)
- x_armB, y_armB = x2 + self.armB*cosB, y2 + self.armB*sinB
+ self.rad = rad
+ def connect(self, posA, posB):
+ x1, y1 = posA
+ x2, y2 = posB
+
+ vertices = [(x1, y1)]
+ rounded = []
+ codes = [Path.MOVETO]
+
+ if self.armA:
+ cosA = math.cos(self.angleA/180.*math.pi)
+ sinA = math.sin(self.angleA/180.*math.pi)
+ #x_armA, y_armB
+ d = self.armA - self.rad
+ rounded.append((x1 + d*cosA, y1 + d*sinA))
+ d = self.armA
+ rounded.append((x1 + d*cosA, y1 + d*sinA))
+
+ if self.armB:
+ cosB = math.cos(self.angleB/180.*math.pi)
+ sinB = math.sin(self.angleB/180.*math.pi)
+ x_armB, y_armB = x2 + self.armB*cosB, y2 + self.armB*sinB
+
+ if rounded:
+ xp, yp = rounded[-1]
+ dx, dy = x_armB - xp, y_armB - yp
+ dd = (dx*dx + dy*dy)**.5
+
+ rounded.append((xp + self.rad*dx/dd, yp + self.rad*dy/dd))
+ vertices.extend(rounded)
+ codes.extend([Path.LINETO,
+ Path.CURVE3,
+ Path.CURVE3])
+ else:
+ xp, yp = vertices[-1]
+ dx, dy = x_armB - xp, y_armB - yp
+ dd = (dx*dx + dy*dy)**.5
+
+ d = dd - self.rad
+ rounded = [(xp + d*dx/dd, yp + d*dy/dd),
+ (x_armB, y_armB)]
+
if rounded:
xp, yp = rounded[-1]
- dx, dy = x_armB - xp, y_armB - yp
+ dx, dy = x2 - xp, y2 - yp
dd = (dx*dx + dy*dy)**.5
rounded.append((xp + self.rad*dx/dd, yp + self.rad*dy/dd))
@@ -2194,528 +2521,677 @@
codes.extend([Path.LINETO,
Path.CURVE3,
Path.CURVE3])
- else:
- xp, yp = vertices[-1]
- dx, dy = x_armB - xp, y_armB - yp
- dd = (dx*dx + dy*dy)**.5
- d = dd - self.rad
- rounded = [(xp + d*dx/dd, yp + d*dy/dd),
- (x_armB, y_armB)]
+ vertices.append((x2, y2))
+ codes.append(Path.LINETO)
- if rounded:
- xp, yp = rounded[-1]
- dx, dy = x2 - xp, y2 - yp
- dd = (dx*dx + dy*dy)**.5
+ return Path(vertices, codes)
- rounded.append((xp + self.rad*dx/dd, yp + self.rad*dy/dd))
- vertices.extend(rounded)
- codes.extend([Path.LINETO,
- Path.CURVE3,
- Path.CURVE3])
+ _style_list["arc"] = Arc
- vertices.append((x2, y2))
- codes.append(Path.LINETO)
+ __doc__ = cbook.dedent(__doc__) % \
+ {"AvailableConnectorstyles": _pprint_styles(_style_list)}
+
- return Path(vertices, codes)
+class ArrowStyle(_Style):
+ """
+ :class:`ArrowStyle` is a container class which defines several
+ arrowstyle classes, which is used to create an arrow path along a
+ given path. These are mainly used with :class:`FancyArrowPatch`.
+ A arrowstyle object can be either created as
+ ArrowStyle.Fancy(head_length=.4, head_width=.4, tail_width=.4)
+ or
-class ArrowTransmuterBase(object):
- """
- Arrow Transmuter Base class
+ ArrowStyle("Fancy", head_length=.4, head_width=.4, tail_width=.4)
- ArrowTransmuterBase and its derivatives are used to make a fancy
- arrow around a given path. The __call__ method returns a path
- (which will be used to create a PathPatch instance) and a boolean
- value indicating the path is open therefore is not fillable. This
- class is not an artist and actual drawing of the fancy arrow is
- done by the FancyArrowPatch class.
+ or
+
+ ArrowStyle("Fancy, head_length=.4, head_width=.4, tail_width=.4")
+ Following classes are defined
+
+ %(AvailableArrowstyles)s
+
+
+ An instance of any arrow style class is an callable object,
+ whose call signature is
+
+ __call__(self, path, mutation_size, linewidth, aspect_ratio=1.)
+
+ and it returns a tuple of a :class:`Path` instance and a boolean
+ value. *path* is a :class:`Path` instance along witch the arrow
+ will be drawn. *mutation_size* and *aspect_ratio* has a same
+ meaning as in :class:`BoxStyle`. *linewidth* is a line width to be
+ stroked. This is meant to be used to correct the location of the
+ head so that it does not overshoot the destination point, but not all
+ classes support it.
"""
- # 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(ArrowTransmuterBase, self).__init__()
+ _style_list = {}
- @staticmethod
- def ensure_quadratic_bezier(path):
- """ Some ArrowTransmuter class only wokrs with a simple
- quaratic bezier curve (created with Arc3Connetion or
- Angle3Connector). This static method is to check if the
- provided path is a simple quadratic bezier curve and returns
- its control points if true.
+ class _Base(object):
"""
- segments = list(path.iter_segments())
- assert len(segments) == 2
+ Arrow Transmuter Base class
- assert segments[0][1] == Path.MOVETO
- assert segments[1][1] == Path.CURVE3
+ ArrowTransmuterBase and its derivatives are used to make a fancy
+ arrow around a given path. The __call__ method returns a path
+ (which will be used to create a PathPatch instance) and a boolean
+ value indicating the path is open therefore is not fillable. This
+ class is not an artist and actual drawing of the fancy arrow is
+ done by the FancyArrowPatch class.
- return list(segments[0][0]) + list(segments[1][0])
+ """
+ # 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 transmute(self, path, mutation_size, linewidth):
+ def __init__(self):
+ super(ArrowStyle....
[truncated message content] |