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