From a7c7893b28fed1958bbaf0deb94e3a366cbf10fb Mon Sep 17 00:00:00 2001 From: Jae-Joon Lee Date: Tue, 3 Jan 2012 11:38:18 +0900 Subject: [PATCH 1/2] implement path_effects for Line2D objects --- lib/matplotlib/lines.py | 50 +++++++++++++++++++++++++++----- lib/matplotlib/patheffects.py | 54 +++++++++++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index 0af0e270c76b..2d69636d2bff 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -144,6 +144,7 @@ def __init__(self, xdata, ydata, pickradius = 5, drawstyle = None, markevery = None, + path_effects = None, **kwargs ): """ @@ -222,6 +223,8 @@ def __init__(self, xdata, ydata, self._invalidy = True self.set_data(xdata, ydata) + self.set_path_effects(path_effects) + def contains(self, mouseevent): """ Test whether the mouse event occurred on the line. The pick @@ -504,7 +507,14 @@ def draw(self, renderer): self._lineFunc = getattr(self, funcname) funcname = self.drawStyles.get(self._drawstyle, '_draw_lines') drawFunc = getattr(self, funcname) - drawFunc(renderer, gc, tpath, affine.frozen()) + + if self.get_path_effects(): + affine_frozen = affine.frozen() + for pe in self.get_path_effects(): + pe_renderer = pe.get_proxy_renderer(renderer) + drawFunc(pe_renderer, gc, tpath, affine_frozen) + else: + drawFunc(renderer, gc, tpath, affine.frozen()) if self._marker: gc = renderer.new_gc() @@ -547,16 +557,31 @@ def draw(self, renderer): w = renderer.points_to_pixels(self._markersize) if marker.get_marker() != ',': # Don't scale for pixels marker_trans = marker_trans.scale(w) - renderer.draw_markers( - gc, marker_path, marker_trans, subsampled, affine.frozen(), - rgbFace) + + if self.get_path_effects(): + affine_frozen = affine.frozen() + for pe in self.get_path_effects(): + pe.draw_markers(renderer, gc, marker_path, marker_trans, + subsampled, affine_frozen, rgbFace) + else: + renderer.draw_markers( + gc, marker_path, marker_trans, subsampled, affine.frozen(), + rgbFace) + alt_marker_path = marker.get_alt_path() if alt_marker_path: alt_marker_trans = marker.get_alt_transform() alt_marker_trans = alt_marker_trans.scale(w) - renderer.draw_markers( - gc, alt_marker_path, alt_marker_trans, subsampled, - affine.frozen(), rgbFaceAlt) + if self.get_path_effects(): + affine_frozen = affine.frozen() + for pe in self.get_path_effects(): + pe.draw_markers(renderer, gc, alt_marker_path, + alt_marker_trans, subsampled, + affine_frozen, rgbFaceAlt) + else: + renderer.draw_markers( + gc, alt_marker_path, alt_marker_trans, subsampled, + affine.frozen(), rgbFaceAlt) gc.restore() @@ -1098,6 +1123,17 @@ def is_dashed(self): 'return True if line is dashstyle' return self._linestyle in ('--', '-.', ':') + 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 + + class VertexSelector: """ Manage the callbacks to maintain a list of selected vertices for diff --git a/lib/matplotlib/patheffects.py b/lib/matplotlib/patheffects.py index 62707c5208da..35f80247bd96 100644 --- a/lib/matplotlib/patheffects.py +++ b/lib/matplotlib/patheffects.py @@ -10,7 +10,6 @@ import matplotlib.transforms as transforms - class _Base(object): """ A base class for PathEffect. Derived must override draw_path method. @@ -22,6 +21,9 @@ def __init__(self): """ super(_Base, self).__init__() + def get_proxy_renderer(self, renderer): + pe_renderer = ProxyRenderer(self, renderer) + return pe_renderer def _update_gc(self, gc, new_gc_dict): new_gc_dict = new_gc_dict.copy() @@ -39,7 +41,7 @@ def _update_gc(self, gc, new_gc_dict): return gc - def draw_path(self, renderer, gc, tpath, affine, rgbFace): + def draw_path(self, renderer, gc, tpath, affine, rgbFace=None): """ Derived should override this method. The argument is same as *draw_path* method of :class:`matplotlib.backend_bases.RendererBase` @@ -71,6 +73,33 @@ def _draw_text_as_path(self, renderer, gc, x, y, s, prop, angle, ismath): gc.set_linewidth(0.0) self.draw_path(renderer, gc, path, transform, rgbFace=color) + def draw_markers(self, renderer, gc, marker_path, marker_trans, path, trans, rgbFace=None): + """ + Draws a marker at each of the vertices in path. This includes + all vertices, including control points on curves. To avoid + that behavior, those vertices should be removed before calling + this function. + + *gc* + the :class:`GraphicsContextBase` instance + + *marker_trans* + is an affine transform applied to the marker. + + *trans* + is an affine transform applied to the path. + + This provides a fallback implementation of draw_markers that + makes multiple calls to :meth:`draw_path`. Some backends may + want to override this method in order to draw the marker only + once and reuse it multiple times. + """ + for vertices, codes in path.iter_segments(trans, simplify=False): + if len(vertices): + x,y = vertices[-2:] + self.draw_path(renderer, gc, marker_path, + marker_trans + transforms.Affine2D().translate(x, y), + rgbFace) # def draw_path_collection(self, renderer, # gc, master_transform, paths, all_transforms, @@ -88,6 +117,27 @@ def _draw_text_as_path(self, renderer, gc, x, y, s, prop, angle, ismath): # transform = transforms.Affine2D(transform.get_matrix()).translate(xo, yo) # self.draw_path(renderer, gc0, path, transform, rgbFace) +class ProxyRenderer(object): + def __init__(self, path_effect, renderer): + self._path_effect = path_effect + self._renderer = renderer + + def draw_path(self, gc, tpath, affine, rgbFace=None): + self._path_effect.draw_path(self._renderer, gc, tpath, affine, rgbFace) + + def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!'): + self._path_effect._draw_text_as_path(self._renderer, + gc, x, y, s, prop, angle, ismath="TeX") + + def draw_text(self, gc, x, y, s, prop, angle, ismath=False): + self._path_effect._draw_text(self.renderer, + gc, x, y, s, prop, angle, ismath) + + def draw_markers(self, gc, marker_path, marker_trans, path, trans, rgbFace=None): + self._path_effect.draw_markers(self._renderer, + gc, marker_path, marker_trans, path, trans, + rgbFace=rgbFace) + class Normal(_Base): """ From 37f9b6bdcf0a44c009511865ec3aaf7e8b568214 Mon Sep 17 00:00:00 2001 From: Jae-Joon Lee Date: Mon, 5 Mar 2012 00:16:29 +0900 Subject: [PATCH 2/2] modify patheffect_demo.py to demonstrate patheffects for Line2D objects --- examples/pylab_examples/patheffect_demo.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/pylab_examples/patheffect_demo.py b/examples/pylab_examples/patheffect_demo.py index 665b9dc8fccf..58bfa08f00f1 100644 --- a/examples/pylab_examples/patheffect_demo.py +++ b/examples/pylab_examples/patheffect_demo.py @@ -17,6 +17,13 @@ foreground="w"), PathEffects.Normal()]) + ax1.grid(True, linestyle="-") + + pe = [PathEffects.withStroke(linewidth=3, + foreground="w")] + for l in ax1.get_xgridlines() + ax1.get_ygridlines(): + l.set_path_effects(pe) + ax2 = plt.subplot(132) arr = np.arange(25).reshape((5,5)) ax2.imshow(arr)