Skip to content

implement path_effects for Line2D objects #655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/pylab_examples/patheffect_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
50 changes: 43 additions & 7 deletions lib/matplotlib/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def __init__(self, xdata, ydata,
pickradius = 5,
drawstyle = None,
markevery = None,
path_effects = None,
**kwargs
):
"""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand Down
54 changes: 52 additions & 2 deletions lib/matplotlib/patheffects.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import matplotlib.transforms as transforms



class _Base(object):
"""
A base class for PathEffect. Derived must override draw_path method.
Expand All @@ -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()
Expand All @@ -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`
Expand Down Expand Up @@ -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,
Expand All @@ -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):
"""
Expand Down