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] |