From: <lee...@us...> - 2009-10-19 06:50:54
|
Revision: 7892 http://matplotlib.svn.sourceforge.net/matplotlib/?rev=7892&view=rev Author: leejjoon Date: 2009-10-19 06:50:44 +0000 (Mon, 19 Oct 2009) Log Message: ----------- Add "path_effects" support for Text and Patch. Modified Paths: -------------- trunk/matplotlib/CHANGELOG trunk/matplotlib/lib/matplotlib/backend_bases.py trunk/matplotlib/lib/matplotlib/patches.py trunk/matplotlib/lib/matplotlib/text.py Added Paths: ----------- trunk/matplotlib/examples/pylab_examples/patheffect_demo.py trunk/matplotlib/lib/matplotlib/patheffects.py Modified: trunk/matplotlib/CHANGELOG =================================================================== --- trunk/matplotlib/CHANGELOG 2009-10-19 04:17:32 UTC (rev 7891) +++ trunk/matplotlib/CHANGELOG 2009-10-19 06:50:44 UTC (rev 7892) @@ -1,6 +1,9 @@ +2009-10-19 Add "path_effects" support for Text and Patch. See + examples/pylab_examples/patheffect_demo.py -JJL + 2009-10-19 Add "use_clabeltext" option to clabel. If True, clabels will be created with ClabelText class, which recalculates - rotation angle of the label during the drawing time. + rotation angle of the label during the drawing time. -JJL 2009-10-16 Make AutoDateFormatter actually use any specified timezone setting.This was only working correctly Added: trunk/matplotlib/examples/pylab_examples/patheffect_demo.py =================================================================== --- trunk/matplotlib/examples/pylab_examples/patheffect_demo.py (rev 0) +++ trunk/matplotlib/examples/pylab_examples/patheffect_demo.py 2009-10-19 06:50:44 UTC (rev 7892) @@ -0,0 +1,37 @@ +import matplotlib.pyplot as plt +import matplotlib.patheffects as PathEffects +import numpy as np + +if 1: + plt.figure(1, figsize=(8,3)) + ax1 = plt.subplot(131) + ax1.imshow([[1,2],[2,3]]) + txt = ax1.annotate("test", (1., 1.), (0., 0), + arrowprops=dict(arrowstyle="->", + connectionstyle="angle3", lw=2), + size=20, ha="center") + + txt.set_path_effects([PathEffects.withStroke(linewidth=3, + foreground="w")]) + txt.arrow_patch.set_path_effects([PathEffects.Stroke(linewidth=5, + foreground="w"), + PathEffects.Normal()]) + + ax2 = plt.subplot(132) + arr = np.arange(25).reshape((5,5)) + ax2.imshow(arr) + cntr = ax2.contour(arr, colors="k") + clbls = ax2.clabel(cntr, fmt="%2.0f", use_clabeltext=True) + plt.setp(clbls, + path_effects=[PathEffects.withStroke(linewidth=3, + foreground="w")]) + + + # shadow as a path effect + ax3 = plt.subplot(133) + p1, = ax3.plot([0, 1], [0, 1]) + leg = ax3.legend([p1], ["Line 1"], fancybox=True, loc=2) + leg.legendPatch.set_path_effects([PathEffects.withSimplePatchShadow()]) + + plt.show() + Modified: trunk/matplotlib/lib/matplotlib/backend_bases.py =================================================================== --- trunk/matplotlib/lib/matplotlib/backend_bases.py 2009-10-19 04:17:32 UTC (rev 7891) +++ trunk/matplotlib/lib/matplotlib/backend_bases.py 2009-10-19 06:50:44 UTC (rev 7892) @@ -381,9 +381,9 @@ self._draw_text_as_path(gc, x, y, s, prop, angle, ismath) - def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath): + def _get_text_path_transform(self, x, y, s, prop, angle, ismath): """ - draw the text by converting them to paths using textpath module. + return the text path and transform *prop* font property @@ -399,7 +399,6 @@ """ text2path = self._text2path - color = gc.get_rgb()[:3] fontsize = self.points_to_pixels(prop.get_size_in_points()) if ismath == "TeX": @@ -418,6 +417,29 @@ fontsize/text2path.FONT_SCALE).\ rotate(angle).translate(x, y) + return path, transform + + + def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath): + """ + draw the text by converting them to paths using textpath module. + + *prop* + font property + + *s* + text to be converted + + *usetex* + If True, use matplotlib usetex mode. + + *ismath* + If True, use mathtext parser. If "TeX", use *usetex* mode. + """ + + path, transform = self._get_text_path_transform(x, y, s, prop, angle, ismath) + color = gc.get_rgb()[:3] + gc.set_linewidth(0.0) self.draw_path(gc, path, transform, rgbFace=color) Modified: trunk/matplotlib/lib/matplotlib/patches.py =================================================================== --- trunk/matplotlib/lib/matplotlib/patches.py 2009-10-19 04:17:32 UTC (rev 7891) +++ trunk/matplotlib/lib/matplotlib/patches.py 2009-10-19 06:50:44 UTC (rev 7892) @@ -53,15 +53,16 @@ return str(self.__class__).split('.')[-1] def __init__(self, - edgecolor=None, - facecolor=None, - linewidth=None, - linestyle=None, - antialiased = None, - hatch = None, - fill=True, - **kwargs - ): + edgecolor=None, + facecolor=None, + linewidth=None, + linestyle=None, + antialiased = None, + hatch = None, + fill=True, + path_effects = None, + **kwargs + ): """ The following kwarg properties are supported @@ -89,6 +90,8 @@ self.fill = fill self._combined_transform = transforms.IdentityTransform() + self.set_path_effects(path_effects) + if len(kwargs): artist.setp(self, **kwargs) def get_verts(self): @@ -324,6 +327,16 @@ 'Return the current hatching pattern' return self._hatch + def set_path_effects(self, path_effects): + """ + set path_effects, which should be a list of instances of + matplotlib.patheffect._Base class or its derivatives. + """ + self._path_effects = path_effects + + def get_path_effects(self): + return self._path_effects + @allow_rasterization def draw(self, renderer): 'Draw the :class:`Patch` to the given *renderer*.' @@ -363,7 +376,11 @@ tpath = transform.transform_path_non_affine(path) affine = transform.get_affine() - renderer.draw_path(gc, tpath, affine, rgbFace) + if self.get_path_effects(): + for path_effect in self.get_path_effects(): + path_effect.draw_path(renderer, gc, tpath, affine, rgbFace) + else: + renderer.draw_path(gc, tpath, affine, rgbFace) gc.restore() renderer.close_group('patch') @@ -3752,11 +3769,19 @@ renderer.open_group('patch', self.get_gid()) - for p, f in zip(path, fillable): - if f: - renderer.draw_path(gc, p, affine, rgbFace) - else: - renderer.draw_path(gc, p, affine, None) + if self.get_path_effects(): + for path_effect in self.get_path_effects(): + for p, f in zip(path, fillable): + if f: + path_effect.draw_path(renderer, gc, p, affine, rgbFace) + else: + path_effect.draw_path(renderer, gc, p, affine, None) + else: + for p, f in zip(path, fillable): + if f: + renderer.draw_path(gc, p, affine, rgbFace) + else: + renderer.draw_path(gc, p, affine, None) gc.restore() Added: trunk/matplotlib/lib/matplotlib/patheffects.py =================================================================== --- trunk/matplotlib/lib/matplotlib/patheffects.py (rev 0) +++ trunk/matplotlib/lib/matplotlib/patheffects.py 2009-10-19 06:50:44 UTC (rev 7892) @@ -0,0 +1,209 @@ +""" +Defines classes for path effects. The path effects are supported in +:class:`~matplotlib.text.Text` and :class:`~matplotlib.patches.Patch` +matplotlib.text.Text. +""" + +from matplotlib.backend_bases import RendererBase +import matplotlib.transforms as transforms + + + +class _Base(object): + """ + A base class for PathEffect. Derived must override draw_path method. + """ + + def __init__(self): + """ + initializtion. + """ + super(_Base, self).__init__() + + + def _update_gc(self, gc, new_gc_dict): + new_gc_dict = new_gc_dict.copy() + + dashes = new_gc_dict.pop("dashes", None) + if dashes: + gc.set_dashes(**dashes) + + for k, v in new_gc_dict.iteritems(): + set_method = getattr(gc, 'set_'+k, None) + if set_method is None or not callable(set_method): + raise AttributeError('Unknown property %s'%k) + set_method(v) + + return gc + + + def draw_path(self, renderer, gc, tpath, affine, rgbFace): + """ + Derived should override this method. The argument is same + as *draw_path* method of :class:`matplotlib.backend_bases.RendererBase` + except the first argument is a renderer. The base definition is :: + + def draw_path(self, renderer, gc, tpath, affine, rgbFace): + renderer.draw_path(gc, tpath, affine, rgbFace) + + """ + renderer.draw_path(gc, tpath, affine, rgbFace) + + def draw_tex(self, renderer, gc, x, y, s, prop, angle, ismath='TeX!'): + self._draw_text_as_path(renderer, gc, x, y, s, prop, angle, ismath="TeX") + + def draw_text(self, renderer, gc, x, y, s, prop, angle, ismath=False): + self._draw_text_as_path(renderer, gc, x, y, s, prop, angle, ismath) + + def _draw_text_as_path(self, renderer, gc, x, y, s, prop, angle, ismath): + + path, transform = RendererBase._get_text_path_transform(renderer, + x, y, s, + prop, angle, + ismath) + color = gc.get_rgb()[:3] + + gc.set_linewidth(0.0) + self.draw_path(renderer, gc, path, transform, rgbFace=color) + + +# def draw_path_collection(self, renderer, +# gc, master_transform, paths, all_transforms, +# offsets, offsetTrans, facecolors, edgecolors, +# linewidths, linestyles, antialiaseds, urls): +# path_ids = [] +# for path, transform in renderer._iter_collection_raw_paths( +# master_transform, paths, all_transforms): +# path_ids.append((path, transform)) + +# for xo, yo, path_id, gc0, rgbFace in renderer._iter_collection( +# gc, path_ids, offsets, offsetTrans, facecolors, edgecolors, +# linewidths, linestyles, antialiaseds, urls): +# path, transform = path_id +# transform = transforms.Affine2D(transform.get_matrix()).translate(xo, yo) +# self.draw_path(renderer, gc0, path, transform, rgbFace) + + +class Normal(_Base): + """ + path effect with no effect + """ + pass + +class Stroke(_Base): + """ + stroke the path with updated gc. + """ + + def __init__(self, **kwargs): + """ + The path will be stroked with its gc updated with the given + keyword arguments, i.e., the keyword arguments should be valid + gc parameter values. + """ + super(Stroke, self).__init__() + self._gc = kwargs + + def draw_path(self, renderer, gc, tpath, affine, rgbFace): + """ + draw the path with update gc. + """ + # Do not modify the input! Use copy instead. + + gc0 = renderer.new_gc() + gc0.copy_properties(gc) + + gc0 = self._update_gc(gc0, self._gc) + renderer.draw_path(gc0, tpath, affine, None) + + +class withStroke(Stroke): + + """ + Same as Stroke, but add a stroke with the original gc at the end. + """ + + def draw_path(self, renderer, gc, tpath, affine, rgbFace): + + Stroke.draw_path(self, renderer, gc, tpath, affine, rgbFace) + renderer.draw_path(gc, tpath, affine, rgbFace) + + +import matplotlib.transforms as mtransforms + +class SimplePatchShadow(_Base): + """ + simple shadow + """ + + def __init__(self, offset_xy=(2,-2), + shadow_rgbFace=None, patch_alpha=0.7, + **kwargs): + """ + """ + super(_Base, self).__init__() + self._offset_xy = offset_xy + self._shadow_rgbFace = shadow_rgbFace + self._patch_alpha = patch_alpha + + self._gc = kwargs + self._offset_tran = mtransforms.Affine2D() + + def draw_path(self, renderer, gc, tpath, affine, rgbFace): + """ + """ + # Do not modify the input! Use copy instead. + + offset_x = renderer.points_to_pixels(self._offset_xy[0]) + offset_y = renderer.points_to_pixels(self._offset_xy[1]) + + affine0 = affine + self._offset_tran.clear().translate(offset_x, offset_y) + + gc0 = renderer.new_gc() + gc0.copy_properties(gc) + + if self._shadow_rgbFace is None: + r,g,b = rgbFace + rho = 0.3 + r = rho*r + g = rho*g + b = rho*b + + shadow_rgbFace = (r,g,b) + else: + shadow_rgbFace = self._shadow_rgbFace + + gc0.set_foreground("none") + gc0.set_alpha(1.-self._patch_alpha) + gc0.set_linewidth(0) + + gc0 = self._update_gc(gc0, self._gc) + renderer.draw_path(gc0, tpath, affine0, shadow_rgbFace) + + +class withSimplePatchShadow(SimplePatchShadow): + """ + simple shadow + """ + + def draw_path(self, renderer, gc, tpath, affine, rgbFace): + + SimplePatchShadow.draw_path(self, renderer, gc, tpath, affine, rgbFace) + + gc1 = renderer.new_gc() + gc1.copy_properties(gc) + gc1.set_alpha(gc1.get_alpha()*self._patch_alpha) + renderer.draw_path(gc1, tpath, affine, rgbFace) + + +if __name__ == '__main__': + clf() + imshow([[1,2],[2,3]]) + #eff = PathEffects.Thicken() + txt = annotate("test", (1., 1.), (0., 0), + arrowprops=dict(arrowstyle="->", connectionstyle="angle3", lw=2), + size=12, ha="center") + txt.set_path_effects([withStroke(linewidth=3, foreground="w")]) + #txt.arrow_patch.set_path_effects([PathEffects.withStroke(width=3, color="w")]) + txt.arrow_patch.set_path_effects([Stroke(linewidth=5, foreground="w"), + Normal()]) Modified: trunk/matplotlib/lib/matplotlib/text.py =================================================================== --- trunk/matplotlib/lib/matplotlib/text.py 2009-10-19 04:17:32 UTC (rev 7891) +++ trunk/matplotlib/lib/matplotlib/text.py 2009-10-19 06:50:44 UTC (rev 7892) @@ -27,6 +27,7 @@ import matplotlib.font_manager as font_manager from matplotlib.ft2font import FT2Font +from matplotlib.backend_bases import RendererBase def _process_text_args(override, fontdict=None, **kwargs): "Return an override dict. See :func:`~pyplot.text' docstring for info" @@ -154,6 +155,7 @@ rotation=None, linespacing=None, rotation_mode=None, + path_effects=None, **kwargs ): """ @@ -172,6 +174,7 @@ if fontproperties is None: fontproperties=FontProperties() elif is_string_like(fontproperties): fontproperties=FontProperties(fontproperties) + self.set_path_effects(path_effects) self.set_text(text) self.set_color(color) self._verticalalignment = verticalalignment @@ -274,17 +277,26 @@ whs = np.zeros((len(lines), 2)) horizLayout = np.zeros((len(lines), 4)) + if self.get_path_effects(): + def get_text_width_height_descent(*kl, **kwargs): + return RendererBase.get_text_width_height_descent(renderer, + *kl, **kwargs) + else: + get_text_width_height_descent = renderer.get_text_width_height_descent + # Find full vertical extent of font, # including ascenders and descenders: - tmp, lp_h, lp_bl = renderer.get_text_width_height_descent( - 'lp', self._fontproperties, ismath=False) + tmp, lp_h, lp_bl = get_text_width_height_descent('lp', + self._fontproperties, + ismath=False) offsety = lp_h * self._linespacing baseline = None for i, line in enumerate(lines): clean_line, ismath = self.is_math_text(line) - w, h, d = renderer.get_text_width_height_descent( - clean_line, self._fontproperties, ismath=ismath) + w, h, d = get_text_width_height_descent(clean_line, + self._fontproperties, + ismath=ismath) if baseline is None: baseline = h - d whs[i] = w, h @@ -387,6 +399,13 @@ self.cached[key] = ret return ret + def set_path_effects(self, path_effects): + self._path_effects = path_effects + + def get_path_effects(self): + return self._path_effects + + def set_bbox(self, rectprops): """ Draw a bounding box around self. rectprops are any settable @@ -558,8 +577,13 @@ y = canvash-y clean_line, ismath = self.is_math_text(line) - renderer.draw_tex(gc, x, y, clean_line, - self._fontproperties, angle) + if self.get_path_effects(): + for path_effect in self.get_path_effects(): + path_effect.draw_tex(renderer, gc, x, y, clean_line, + self._fontproperties, angle) + else: + renderer.draw_tex(gc, x, y, clean_line, + self._fontproperties, angle) renderer.close_group('text') return @@ -570,9 +594,15 @@ y = canvash-y clean_line, ismath = self.is_math_text(line) - renderer.draw_text(gc, x, y, clean_line, - self._fontproperties, angle, - ismath=ismath) + if self.get_path_effects(): + for path_effect in self.get_path_effects(): + path_effect.draw_text(renderer, gc, x, y, clean_line, + self._fontproperties, angle, + ismath=ismath) + else: + renderer.draw_text(gc, x, y, clean_line, + self._fontproperties, angle, + ismath=ismath) gc.restore() renderer.close_group('text') This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |