Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ae7cdae

Browse files
committed
Add "path_effects" support for Text and Patch.
svn path=/trunk/matplotlib/; revision=7892
1 parent fc4db3d commit ae7cdae

6 files changed

Lines changed: 354 additions & 28 deletions

File tree

CHANGELOG

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
2009-10-19 Add "path_effects" support for Text and Patch. See
2+
examples/pylab_examples/patheffect_demo.py -JJL
3+
14
2009-10-19 Add "use_clabeltext" option to clabel. If True, clabels
25
will be created with ClabelText class, which recalculates
3-
rotation angle of the label during the drawing time.
6+
rotation angle of the label during the drawing time. -JJL
47

58
2009-10-16 Make AutoDateFormatter actually use any specified
69
timezone setting.This was only working correctly
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import matplotlib.pyplot as plt
2+
import matplotlib.patheffects as PathEffects
3+
import numpy as np
4+
5+
if 1:
6+
plt.figure(1, figsize=(8,3))
7+
ax1 = plt.subplot(131)
8+
ax1.imshow([[1,2],[2,3]])
9+
txt = ax1.annotate("test", (1., 1.), (0., 0),
10+
arrowprops=dict(arrowstyle="->",
11+
connectionstyle="angle3", lw=2),
12+
size=20, ha="center")
13+
14+
txt.set_path_effects([PathEffects.withStroke(linewidth=3,
15+
foreground="w")])
16+
txt.arrow_patch.set_path_effects([PathEffects.Stroke(linewidth=5,
17+
foreground="w"),
18+
PathEffects.Normal()])
19+
20+
ax2 = plt.subplot(132)
21+
arr = np.arange(25).reshape((5,5))
22+
ax2.imshow(arr)
23+
cntr = ax2.contour(arr, colors="k")
24+
clbls = ax2.clabel(cntr, fmt="%2.0f", use_clabeltext=True)
25+
plt.setp(clbls,
26+
path_effects=[PathEffects.withStroke(linewidth=3,
27+
foreground="w")])
28+
29+
30+
# shadow as a path effect
31+
ax3 = plt.subplot(133)
32+
p1, = ax3.plot([0, 1], [0, 1])
33+
leg = ax3.legend([p1], ["Line 1"], fancybox=True, loc=2)
34+
leg.legendPatch.set_path_effects([PathEffects.withSimplePatchShadow()])
35+
36+
plt.show()
37+

lib/matplotlib/backend_bases.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,9 +381,9 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False):
381381

382382
self._draw_text_as_path(gc, x, y, s, prop, angle, ismath)
383383

384-
def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
384+
def _get_text_path_transform(self, x, y, s, prop, angle, ismath):
385385
"""
386-
draw the text by converting them to paths using textpath module.
386+
return the text path and transform
387387
388388
*prop*
389389
font property
@@ -399,7 +399,6 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
399399
"""
400400

401401
text2path = self._text2path
402-
color = gc.get_rgb()[:3]
403402
fontsize = self.points_to_pixels(prop.get_size_in_points())
404403

405404
if ismath == "TeX":
@@ -418,6 +417,29 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
418417
fontsize/text2path.FONT_SCALE).\
419418
rotate(angle).translate(x, y)
420419

420+
return path, transform
421+
422+
423+
def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
424+
"""
425+
draw the text by converting them to paths using textpath module.
426+
427+
*prop*
428+
font property
429+
430+
*s*
431+
text to be converted
432+
433+
*usetex*
434+
If True, use matplotlib usetex mode.
435+
436+
*ismath*
437+
If True, use mathtext parser. If "TeX", use *usetex* mode.
438+
"""
439+
440+
path, transform = self._get_text_path_transform(x, y, s, prop, angle, ismath)
441+
color = gc.get_rgb()[:3]
442+
421443
gc.set_linewidth(0.0)
422444
self.draw_path(gc, path, transform, rgbFace=color)
423445

lib/matplotlib/patches.py

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,16 @@ def __str__(self):
5353
return str(self.__class__).split('.')[-1]
5454

5555
def __init__(self,
56-
edgecolor=None,
57-
facecolor=None,
58-
linewidth=None,
59-
linestyle=None,
60-
antialiased = None,
61-
hatch = None,
62-
fill=True,
63-
**kwargs
64-
):
56+
edgecolor=None,
57+
facecolor=None,
58+
linewidth=None,
59+
linestyle=None,
60+
antialiased = None,
61+
hatch = None,
62+
fill=True,
63+
path_effects = None,
64+
**kwargs
65+
):
6566
"""
6667
The following kwarg properties are supported
6768
@@ -89,6 +90,8 @@ def __init__(self,
8990
self.fill = fill
9091
self._combined_transform = transforms.IdentityTransform()
9192

93+
self.set_path_effects(path_effects)
94+
9295
if len(kwargs): artist.setp(self, **kwargs)
9396

9497
def get_verts(self):
@@ -324,6 +327,16 @@ def get_hatch(self):
324327
'Return the current hatching pattern'
325328
return self._hatch
326329

330+
def set_path_effects(self, path_effects):
331+
"""
332+
set path_effects, which should be a list of instances of
333+
matplotlib.patheffect._Base class or its derivatives.
334+
"""
335+
self._path_effects = path_effects
336+
337+
def get_path_effects(self):
338+
return self._path_effects
339+
327340
@allow_rasterization
328341
def draw(self, renderer):
329342
'Draw the :class:`Patch` to the given *renderer*.'
@@ -363,7 +376,11 @@ def draw(self, renderer):
363376
tpath = transform.transform_path_non_affine(path)
364377
affine = transform.get_affine()
365378

366-
renderer.draw_path(gc, tpath, affine, rgbFace)
379+
if self.get_path_effects():
380+
for path_effect in self.get_path_effects():
381+
path_effect.draw_path(renderer, gc, tpath, affine, rgbFace)
382+
else:
383+
renderer.draw_path(gc, tpath, affine, rgbFace)
367384

368385
gc.restore()
369386
renderer.close_group('patch')
@@ -3752,11 +3769,19 @@ def draw(self, renderer):
37523769

37533770
renderer.open_group('patch', self.get_gid())
37543771

3755-
for p, f in zip(path, fillable):
3756-
if f:
3757-
renderer.draw_path(gc, p, affine, rgbFace)
3758-
else:
3759-
renderer.draw_path(gc, p, affine, None)
3772+
if self.get_path_effects():
3773+
for path_effect in self.get_path_effects():
3774+
for p, f in zip(path, fillable):
3775+
if f:
3776+
path_effect.draw_path(renderer, gc, p, affine, rgbFace)
3777+
else:
3778+
path_effect.draw_path(renderer, gc, p, affine, None)
3779+
else:
3780+
for p, f in zip(path, fillable):
3781+
if f:
3782+
renderer.draw_path(gc, p, affine, rgbFace)
3783+
else:
3784+
renderer.draw_path(gc, p, affine, None)
37603785

37613786

37623787
gc.restore()

lib/matplotlib/patheffects.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
"""
2+
Defines classes for path effects. The path effects are supported in
3+
:class:`~matplotlib.text.Text` and :class:`~matplotlib.patches.Patch`
4+
matplotlib.text.Text.
5+
"""
6+
7+
from matplotlib.backend_bases import RendererBase
8+
import matplotlib.transforms as transforms
9+
10+
11+
12+
class _Base(object):
13+
"""
14+
A base class for PathEffect. Derived must override draw_path method.
15+
"""
16+
17+
def __init__(self):
18+
"""
19+
initializtion.
20+
"""
21+
super(_Base, self).__init__()
22+
23+
24+
def _update_gc(self, gc, new_gc_dict):
25+
new_gc_dict = new_gc_dict.copy()
26+
27+
dashes = new_gc_dict.pop("dashes", None)
28+
if dashes:
29+
gc.set_dashes(**dashes)
30+
31+
for k, v in new_gc_dict.iteritems():
32+
set_method = getattr(gc, 'set_'+k, None)
33+
if set_method is None or not callable(set_method):
34+
raise AttributeError('Unknown property %s'%k)
35+
set_method(v)
36+
37+
return gc
38+
39+
40+
def draw_path(self, renderer, gc, tpath, affine, rgbFace):
41+
"""
42+
Derived should override this method. The argument is same
43+
as *draw_path* method of :class:`matplotlib.backend_bases.RendererBase`
44+
except the first argument is a renderer. The base definition is ::
45+
46+
def draw_path(self, renderer, gc, tpath, affine, rgbFace):
47+
renderer.draw_path(gc, tpath, affine, rgbFace)
48+
49+
"""
50+
renderer.draw_path(gc, tpath, affine, rgbFace)
51+
52+
def draw_tex(self, renderer, gc, x, y, s, prop, angle, ismath='TeX!'):
53+
self._draw_text_as_path(renderer, gc, x, y, s, prop, angle, ismath="TeX")
54+
55+
def draw_text(self, renderer, gc, x, y, s, prop, angle, ismath=False):
56+
self._draw_text_as_path(renderer, gc, x, y, s, prop, angle, ismath)
57+
58+
def _draw_text_as_path(self, renderer, gc, x, y, s, prop, angle, ismath):
59+
60+
path, transform = RendererBase._get_text_path_transform(renderer,
61+
x, y, s,
62+
prop, angle,
63+
ismath)
64+
color = gc.get_rgb()[:3]
65+
66+
gc.set_linewidth(0.0)
67+
self.draw_path(renderer, gc, path, transform, rgbFace=color)
68+
69+
70+
# def draw_path_collection(self, renderer,
71+
# gc, master_transform, paths, all_transforms,
72+
# offsets, offsetTrans, facecolors, edgecolors,
73+
# linewidths, linestyles, antialiaseds, urls):
74+
# path_ids = []
75+
# for path, transform in renderer._iter_collection_raw_paths(
76+
# master_transform, paths, all_transforms):
77+
# path_ids.append((path, transform))
78+
79+
# for xo, yo, path_id, gc0, rgbFace in renderer._iter_collection(
80+
# gc, path_ids, offsets, offsetTrans, facecolors, edgecolors,
81+
# linewidths, linestyles, antialiaseds, urls):
82+
# path, transform = path_id
83+
# transform = transforms.Affine2D(transform.get_matrix()).translate(xo, yo)
84+
# self.draw_path(renderer, gc0, path, transform, rgbFace)
85+
86+
87+
class Normal(_Base):
88+
"""
89+
path effect with no effect
90+
"""
91+
pass
92+
93+
class Stroke(_Base):
94+
"""
95+
stroke the path with updated gc.
96+
"""
97+
98+
def __init__(self, **kwargs):
99+
"""
100+
The path will be stroked with its gc updated with the given
101+
keyword arguments, i.e., the keyword arguments should be valid
102+
gc parameter values.
103+
"""
104+
super(Stroke, self).__init__()
105+
self._gc = kwargs
106+
107+
def draw_path(self, renderer, gc, tpath, affine, rgbFace):
108+
"""
109+
draw the path with update gc.
110+
"""
111+
# Do not modify the input! Use copy instead.
112+
113+
gc0 = renderer.new_gc()
114+
gc0.copy_properties(gc)
115+
116+
gc0 = self._update_gc(gc0, self._gc)
117+
renderer.draw_path(gc0, tpath, affine, None)
118+
119+
120+
class withStroke(Stroke):
121+
122+
"""
123+
Same as Stroke, but add a stroke with the original gc at the end.
124+
"""
125+
126+
def draw_path(self, renderer, gc, tpath, affine, rgbFace):
127+
128+
Stroke.draw_path(self, renderer, gc, tpath, affine, rgbFace)
129+
renderer.draw_path(gc, tpath, affine, rgbFace)
130+
131+
132+
import matplotlib.transforms as mtransforms
133+
134+
class SimplePatchShadow(_Base):
135+
"""
136+
simple shadow
137+
"""
138+
139+
def __init__(self, offset_xy=(2,-2),
140+
shadow_rgbFace=None, patch_alpha=0.7,
141+
**kwargs):
142+
"""
143+
"""
144+
super(_Base, self).__init__()
145+
self._offset_xy = offset_xy
146+
self._shadow_rgbFace = shadow_rgbFace
147+
self._patch_alpha = patch_alpha
148+
149+
self._gc = kwargs
150+
self._offset_tran = mtransforms.Affine2D()
151+
152+
def draw_path(self, renderer, gc, tpath, affine, rgbFace):
153+
"""
154+
"""
155+
# Do not modify the input! Use copy instead.
156+
157+
offset_x = renderer.points_to_pixels(self._offset_xy[0])
158+
offset_y = renderer.points_to_pixels(self._offset_xy[1])
159+
160+
affine0 = affine + self._offset_tran.clear().translate(offset_x, offset_y)
161+
162+
gc0 = renderer.new_gc()
163+
gc0.copy_properties(gc)
164+
165+
if self._shadow_rgbFace is None:
166+
r,g,b = rgbFace
167+
rho = 0.3
168+
r = rho*r
169+
g = rho*g
170+
b = rho*b
171+
172+
shadow_rgbFace = (r,g,b)
173+
else:
174+
shadow_rgbFace = self._shadow_rgbFace
175+
176+
gc0.set_foreground("none")
177+
gc0.set_alpha(1.-self._patch_alpha)
178+
gc0.set_linewidth(0)
179+
180+
gc0 = self._update_gc(gc0, self._gc)
181+
renderer.draw_path(gc0, tpath, affine0, shadow_rgbFace)
182+
183+
184+
class withSimplePatchShadow(SimplePatchShadow):
185+
"""
186+
simple shadow
187+
"""
188+
189+
def draw_path(self, renderer, gc, tpath, affine, rgbFace):
190+
191+
SimplePatchShadow.draw_path(self, renderer, gc, tpath, affine, rgbFace)
192+
193+
gc1 = renderer.new_gc()
194+
gc1.copy_properties(gc)
195+
gc1.set_alpha(gc1.get_alpha()*self._patch_alpha)
196+
renderer.draw_path(gc1, tpath, affine, rgbFace)
197+
198+
199+
if __name__ == '__main__':
200+
clf()
201+
imshow([[1,2],[2,3]])
202+
#eff = PathEffects.Thicken()
203+
txt = annotate("test", (1., 1.), (0., 0),
204+
arrowprops=dict(arrowstyle="->", connectionstyle="angle3", lw=2),
205+
size=12, ha="center")
206+
txt.set_path_effects([withStroke(linewidth=3, foreground="w")])
207+
#txt.arrow_patch.set_path_effects([PathEffects.withStroke(width=3, color="w")])
208+
txt.arrow_patch.set_path_effects([Stroke(linewidth=5, foreground="w"),
209+
Normal()])

0 commit comments

Comments
 (0)