Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Path effects update #2462

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

Merged
merged 2 commits into from
Nov 4, 2013
Merged
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
17 changes: 13 additions & 4 deletions 17 doc/api/api_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Changes in 1.4.x
================

* A major refactoring of the axes module was made. The axes module has been
splitted into smaller modules:
split into smaller modules:

- the `_base` module, which contains a new private _AxesBase class. This
class contains all methods except plotting and labelling methods.
Expand All @@ -30,8 +30,7 @@ original location:
- math -> `import math`
- ma -> `from numpy import ma`
- cbook -> `from matplotlib import cbook`
- division -> `from __future__ import division`
- docstring -> `from matplotlib impotr docstring`
- docstring -> `from matplotlib import docstring`
- is_sequence_of_strings -> `from matplotlib.cbook import is_sequence_of_strings`
- is_string_like -> `from matplotlib.cbook import is_string_like`
- iterable -> `from matplotlib.cbook import iterable`
Expand All @@ -41,7 +40,7 @@ original location:
- mcoll -> `from matplotlib import collections as mcoll`
- mcolors -> `from matplotlib import colors as mcolors`
- mcontour -> `from matplotlib import contour as mcontour`
- mpatches -> `from matplotlib import patchs as mpatches`
- mpatches -> `from matplotlib import patches as mpatches`
- mpath -> `from matplotlib import path as mpath`
- mquiver -> `from matplotlib import quiver as mquiver`
- mstack -> `from matplotlib import stack as mstack`
Expand All @@ -54,6 +53,16 @@ original location:
'open-close-high-low' order of quotes, and what the module used and the later
is 'open-high-low-close' order of quotes, which is the standard in finance.

* For consistency the ``face_alpha`` keyword to
:class:`matplotlib.patheffects.SimplePatchShadow` has been deprecated in
favour of the ``alpha`` keyword. Similarly, the keyword ``offset_xy`` is now
named ``offset`` across all :class:`~matplotlib.patheffects.AbstractPathEffect`s.
``matplotlib.patheffects._Base`` has
been renamed to :class:`matplotlib.patheffects.AbstractPathEffect`.
``matplotlib.patheffect.ProxyRenderer`` has been renamed to
:class:`matplotlib.patheffects.PathEffectRenderer` and is now a full
RendererBase subclass.

* The artist used to draw the outline of a `colorbar` has been changed
from a `matplotlib.lines.Line2D` to `matplotlib.patches.Polygon`,
thus `colorbar.ColorbarBase.outline` is now a
Expand Down
1 change: 1 addition & 0 deletions 1 doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
mathtext_api.rst
mlab_api.rst
path_api.rst
patheffects_api.rst
pyplot_api.rst
sankey_api.rst
spines_api.rst
Expand Down
12 changes: 12 additions & 0 deletions 12 doc/api/patheffects_api.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
***********
patheffects
***********


:mod:`matplotlib.patheffects`
=======================

.. automodule:: matplotlib.patheffects
:members:
:undoc-members:
:show-inheritance:
1 change: 1 addition & 0 deletions 1 doc/users/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ User's Guide
transforms_tutorial.rst
path_tutorial.rst
annotations_guide.rst
patheffects_guide.rst
recipes.rst
screenshots.rst
whats_new.rst
Expand Down
134 changes: 134 additions & 0 deletions 134 doc/users/patheffects_guide.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
.. _patheffects-guide:

******************
Path effects guide
******************

.. py:module:: matplotlib.patheffects


Matplotlib's :mod:`~matplotlib.patheffects` module provides functionality to
apply a multiple draw stage to any Artist which can be rendered via a
:class:`~matplotlib.path.Path`.

Artists which can have a path effect applied to them include :class:`~matplotlib.patches.Patch`,
:class:`~matplotlib.lines.Line2D`, :class:`~matplotlib.collections.Collection` and even
:class:`~matplotlib.text.Text`. Each artist's path effects can be controlled via the
``set_path_effects`` method (:class:`~matplotlib.artist.Artist.set_path_effects`), which takes
an iterable of :class:`AbstractPathEffect` instances.

The simplest path effect is the :class:`Normal` effect, which simply
draws the artist without any effect:

.. plot::
:include-source:

import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects

fig = plt.figure(figsize=(5, 1.5))
text = fig.text(0.5, 0.5, 'Hello path effects world!\nThis is the normal '
'path effect.\nPretty dull, huh?',
ha='center', va='center', size=20)
text.set_path_effects([path_effects.Normal()])
plt.show()

Whilst the plot doesn't look any different to what you would expect without any path
effects, the drawing of the text now been changed to use the the path effects
framework, opening up the possibilities for more interesting examples.

Adding a shadow
---------------

A far more interesting path effect than :class:`Normal` is the
drop-shadow, which we can apply to any of our path based artists. The classes
:class:`SimplePatchShadow` and
:class:`SimpleLineShadow` do precisely this by drawing either a filled
patch or a line patch below the original artist:

.. plot::
:include-source:

import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects

text = plt.text(0.5, 0.5, 'Hello path effects world!',
path_effects=[path_effects.withSimplePatchShadow()])

plt.plot([0, 3, 2, 5], linewidth=5, color='blue',
path_effects=[path_effects.SimpleLineShadow(),
path_effects.Normal()])
plt.show()


Notice the two approaches to setting the path effects in this example. The
first uses the ``with*`` classes to include the desired functionality automatically
followed with the "normal" effect, whereas the latter explicitly defines the two path
effects to draw.

Making an artist stand out
--------------------------

One nice way of making artists visually stand out is to draw an outline in a bold
color below the actual artist. The :class:`Stroke` path effect
makes this a relatively simple task:

.. plot::
:include-source:

import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects

fig = plt.figure(figsize=(7, 1))
text = fig.text(0.5, 0.5, 'This text stands out because of\n'
'its black border.', color='white',
ha='center', va='center', size=30)
text.set_path_effects([path_effects.Stroke(linewidth=3, foreground='black'),
path_effects.Normal()])
plt.show()

It is important to note that this effect only works because we have drawn the text
path twice; once with a thick black line, and then once with the original text
path on top.

You may have noticed that the keywords to :class:`Stroke` and
:class:`SimplePatchShadow` and :class:`SimpleLineShadow` are not the usual Artist
keywords (such as ``facecolor`` and ``edgecolor`` etc.). This is because with these
path effects we are operating at lower level of matplotlib. In fact, the keywords
which are accepted are those for a :class:`matplotlib.backend_bases.GraphicsContextBase`
instance, which have been designed for making it easy to create new backends - and not
for its user interface.


Greater control of the path effect artist
-----------------------------------------

As already mentioned, some of the path effects operate at a lower level than most users
will be used to, meaning that setting keywords such as ``facecolor`` and ``edgecolor``
raise an AttributeError. Luckily there is a generic :class:`PathPatchEffect` path effect
which creates a :class:`~matplotlib.patches.PathPatch` class with the original path.
The keywords to this effect are identical to those of :class:`~matplotlib.patches.PathPatch`:

.. plot::
:include-source:

import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects

fig = plt.figure(figsize=(8, 1))
t = fig.text(0.02, 0.5, 'Hatch shadow', fontsize=75, weight=1000, va='center')
t.set_path_effects([path_effects.PathPatchEffect(offset=(4, -4), hatch='xxxx',
facecolor='gray'),
path_effects.PathPatchEffect(edgecolor='white', linewidth=1.1,
facecolor='black')])
plt.show()


..
Headings for future consideration:

Implementing a custom path effect
---------------------------------

What is going on under the hood
--------------------------------
7 changes: 7 additions & 0 deletions 7 doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ revision, see the :ref:`github-stats`.
new in matplotlib-1.4
=====================

Documentation changes
---------------------

Phil Elson rewrote of the documentation and userguide for both Legend and PathEffects (links needed).


New plotting features
---------------------

Expand Down Expand Up @@ -74,6 +80,7 @@ and :func:`matplotlib.dates.datestr2num`. Support is also added to the unit
conversion interfaces :class:`matplotlib.dates.DateConverter` and
:class:`matplotlib.units.Registry`.


Configuration (rcParams)
------------------------

Expand Down
12 changes: 7 additions & 5 deletions 12 lib/matplotlib/backend_bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,25 @@ def mainloop(self):
pass


class RendererBase:
class RendererBase(object):
"""An abstract base class to handle drawing/rendering operations.

The following methods *must* be implemented in the backend:
The following methods must be implemented in the backend for full
functionality (though just implementing :meth:`draw_path` alone would
give a highly capable backend):

* :meth:`draw_path`
* :meth:`draw_image`
* :meth:`draw_text`
* :meth:`get_text_width_height_descent`
* :meth:`draw_gouraud_triangle`

The following methods *should* be implemented in the backend for
optimization reasons:

* :meth:`draw_text`
* :meth:`draw_markers`
* :meth:`draw_path_collection`
* :meth:`draw_quad_mesh`

"""
def __init__(self):
self._texmanager = None
Expand Down Expand Up @@ -558,7 +561,6 @@ def _draw_text_as_path(self, gc, x, y, s, prop, angle, ismath):
*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()
Expand Down
4 changes: 2 additions & 2 deletions 4 lib/matplotlib/backends/backend_agg.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def draw_path(self, gc, path, transform, rgbFace=None):
nmax = rcParams['agg.path.chunksize'] # here at least for testing
npts = path.vertices.shape[0]
if (nmax > 100 and npts > nmax and path.should_simplify and
rgbFace is None and gc.get_hatch() is None):
rgbFace is None and gc.get_hatch() is None):
nch = np.ceil(npts/float(nmax))
chsize = int(np.ceil(npts/nch))
i0 = np.arange(0, npts, chsize)
Expand Down Expand Up @@ -196,7 +196,7 @@ def get_text_width_height_descent(self, s, prop, ismath):
get the width and height in display coords of the string s
with FontPropertry prop

# passing rgb is a little hack to make cacheing in the
# passing rgb is a little hack to make caching in the
# texmanager more efficient. It is not meant to be used
# outside the backend
"""
Expand Down
7 changes: 4 additions & 3 deletions 7 lib/matplotlib/backends/backend_mixed.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
unicode_literals)

import six

from matplotlib._image import frombuffer
from matplotlib.backends.backend_agg import RendererAgg
from matplotlib.tight_bbox import process_figure_for_rasterizing


class MixedModeRenderer(object):
"""
A helper class to implement a renderer that switches between
Expand Down Expand Up @@ -47,7 +49,7 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
self._raster_renderer = None
self._rasterizing = 0

# A renference to the figure is needed as we need to change
# A reference to the figure is needed as we need to change
# the figure dpi before and after the rasterization. Although
# this looks ugly, I couldn't find a better solution. -JJL
self.figure=figure
Expand All @@ -65,6 +67,7 @@ def __init__(self, figure, width, height, dpi, vector_renderer,
option_image_nocomposite points_to_pixels strip_math
start_filter stop_filter draw_gouraud_triangle
draw_gouraud_triangles option_scale_image
_text2path _get_text_path_transform height width
""".split()
def _set_current_renderer(self, renderer):
self._renderer = renderer
Expand All @@ -75,7 +78,6 @@ def _set_current_renderer(self, renderer):
renderer.start_rasterizing = self.start_rasterizing
renderer.stop_rasterizing = self.stop_rasterizing


def start_rasterizing(self):
"""
Enter "raster" mode. All subsequent drawing commands (until
Expand Down Expand Up @@ -103,7 +105,6 @@ def start_rasterizing(self):
self._set_current_renderer(self._raster_renderer)
self._rasterizing += 1


def stop_rasterizing(self):
"""
Exit "raster" mode. All of the drawing that was done since
Expand Down
11 changes: 7 additions & 4 deletions 11 lib/matplotlib/backends/backend_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -1449,10 +1449,10 @@ def finalize(self):
self.file.output(*self.gc.finalize())

def check_gc(self, gc, fillcolor=None):
orig_fill = gc._fillcolor
orig_fill = getattr(gc, '_fillcolor', (0., 0., 0.))
gc._fillcolor = fillcolor

orig_alphas = gc._effective_alphas
orig_alphas = getattr(gc, '_effective_alphas', (1.0, 1.0))

if gc._forced_alpha:
gc._effective_alphas = (gc._alpha, gc._alpha)
Expand Down Expand Up @@ -2207,8 +2207,11 @@ def copy_properties(self, other):
Copy properties of other into self.
"""
GraphicsContextBase.copy_properties(self, other)
self._fillcolor = other._fillcolor
self._effective_alphas = other._effective_alphas
fillcolor = getattr(other, '_fillcolor', self._fillcolor)
effective_alphas = getattr(other, '_effective_alphas',
self._effective_alphas)
self._fillcolor = fillcolor
self._effective_alphas = effective_alphas

def finalize(self):
"""
Expand Down
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.