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.
------------------------------------------------------------------------------
Come build with us! The BlackBerry(R) Developer Conference in SF, CA
is the only developer event you need to attend this year. Jumpstart your
developing skills, take BlackBerry mobile applications to market and stay
ahead of the curve. Join us from November 9 - 12, 2009. Register now!
http://p.sf.net/sfu/devconference
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins