Skip to content

Commit 6b442b8

Browse files
committed
Merge pull request #1329 from mdboom/wiggles
Add a "sketch" path filter for XKCD style graphics.
2 parents 47193ee + 4a61f50 commit 6b442b8

37 files changed

+2372
-123
lines changed

CHANGELOG

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
2013-04-15 Added 'axes.xmargin' and 'axes.ymargin' to rpParams to set default
1717
margins on auto-scaleing. - TAC
1818

19+
2013-04-16 Added patheffect support for Line2D objects. -JJL
20+
1921
2013-03-19 Added support for passing `linestyle` kwarg to `step` so all `plot`
2022
kwargs are passed to the underlying `plot` call. -TAC
2123

doc/api/api_changes.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ For new features that were added to matplotlib, please see
1515
Changes in 1.3.x
1616
================
1717

18-
* Fixed a bug in setting the position for the right/top spine with data
18+
* The `font.*` rcParams now affect only text objects created after the
19+
rcParam has been set, and will not retroactively affect already
20+
existing text objects. This brings their behavior in line with most
21+
other rcParams.
22+
23+
* Fixed a bug in setting the position for the right/top spine with data
1924
position type. Previously, it would draw the right or top spine at
2025
+1 data offset.
2126

doc/users/whats_new.rst

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,30 @@ revision, see the :ref:`github-stats`.
2121

2222
new in matplotlib-1.3
2323
=====================
24+
`xkcd`-style sketch plotting
25+
----------------------------
26+
27+
To give your plots a sense of authority that they may be missing,
28+
Michael Droettboom (inspired by the work of many others in
29+
:ghpull:`1329`) has added an `xkcd-style <http://xkcd.com/>`_ sketch
30+
plotting mode. To use it, simply call `pyplot.xkcd` before creating
31+
your plot.
32+
33+
.. plot:: mpl_examples/showcase/xkcd.py
34+
35+
Path effects on lines
36+
---------------------
37+
Thanks to Jae-Joon Lee, path effects now also work on plot lines.
38+
39+
.. plot:: mpl_examples/pylab_examples/patheffect_demo.py
40+
41+
Changes to font rcParams
42+
------------------------
43+
The `font.*` rcParams now affect only text objects created after the
44+
rcParam has been set, and will not retroactively affect already
45+
existing text objects. This brings their behavior in line with most
46+
other rcParams.
47+
2448
``axes.xmargin`` and ``axes.ymargin`` added to rcParams
2549
-------------------------------------------------------
2650
``rcParam`` values (``axes.xmargin`` and ``axes.ymargin``) were added
@@ -148,8 +172,8 @@ the bottom of the text bounding box.
148172

149173
``savefig.jpeg_quality`` added to rcParams
150174
------------------------------------------------------------------------------
151-
``rcParam`` value ``savefig.jpeg_quality`` was added so that the user can
152-
configure the default quality used when a figure is written as a JPEG. The
175+
``rcParam`` value ``savefig.jpeg_quality`` was added so that the user can
176+
configure the default quality used when a figure is written as a JPEG. The
153177
default quality is 95; previously, the default quality was 75. This change
154178
minimizes the artifacting inherent in JPEG images, particularly with images
155179
that have sharp changes in color as plots often do.

examples/pylab_examples/patheffect_demo.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,28 @@
1313

1414
txt.set_path_effects([PathEffects.withStroke(linewidth=3,
1515
foreground="w")])
16-
txt.arrow_patch.set_path_effects([PathEffects.Stroke(linewidth=5,
17-
foreground="w"),
18-
PathEffects.Normal()])
16+
txt.arrow_patch.set_path_effects([
17+
PathEffects.Stroke(linewidth=5, foreground="w"),
18+
PathEffects.Normal()])
19+
20+
ax1.grid(True, linestyle="-")
21+
22+
pe = [PathEffects.withStroke(linewidth=3,
23+
foreground="w")]
24+
for l in ax1.get_xgridlines() + ax1.get_ygridlines():
25+
l.set_path_effects(pe)
1926

2027
ax2 = plt.subplot(132)
2128
arr = np.arange(25).reshape((5,5))
2229
ax2.imshow(arr)
2330
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")])
2831

32+
plt.setp(cntr.collections, path_effects=[
33+
PathEffects.withStroke(linewidth=3, foreground="w")])
34+
35+
clbls = ax2.clabel(cntr, fmt="%2.0f", use_clabeltext=True)
36+
plt.setp(clbls, path_effects=[
37+
PathEffects.withStroke(linewidth=3, foreground="w")])
2938

3039
# shadow as a path effect
3140
ax3 = plt.subplot(133)
@@ -34,4 +43,3 @@
3443
leg.legendPatch.set_path_effects([PathEffects.withSimplePatchShadow()])
3544

3645
plt.show()
37-

examples/pylab_examples/simple_plot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
t = arange(0.0, 2.0, 0.01)
44
s = sin(2*pi*t)
5-
plot(t, s, linewidth=1.0)
5+
plot(t, s)
66

77
xlabel('time (s)')
88
ylabel('voltage (mV)')

examples/pylab_examples/to_numeric.py

100644100755
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@
3030
X.shape = h, w, 3
3131

3232
im = Image.fromstring( "RGB", (w,h), s)
33-
im.show()
3433

34+
# Uncomment this line to display the image using ImageMagick's
35+
# `display` tool.
36+
# im.show()

examples/showcase/xkcd.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
4+
with plt.xkcd():
5+
# Based on "Stove Ownership" from XKCD by Randall Monroe
6+
# http://xkcd.com/418/
7+
8+
fig = plt.figure()
9+
ax = fig.add_axes((0.1, 0.2, 0.8, 0.7))
10+
ax.spines['right'].set_color('none')
11+
ax.spines['top'].set_color('none')
12+
plt.xticks([])
13+
plt.yticks([])
14+
ax.set_ylim([-30, 10])
15+
16+
data = np.ones(100)
17+
data[70:] -= np.arange(30)
18+
19+
plt.annotate(
20+
'THE DAY I REALIZED\nI COULD COOK BACON\nWHENEVER I WANTED',
21+
xy=(70, 1), arrowprops=dict(arrowstyle='->'), xytext=(15, -10))
22+
23+
plt.plot(data)
24+
25+
plt.xlabel('time')
26+
plt.ylabel('my overall health')
27+
fig.text(
28+
0.5, 0.05,
29+
'"Stove Ownership" from xkcd by Randall Monroe',
30+
ha='center')
31+
32+
# Based on "The Data So Far" from XKCD by Randall Monroe
33+
# http://xkcd.com/373/
34+
35+
fig = plt.figure()
36+
ax = fig.add_axes((0.1, 0.2, 0.8, 0.7))
37+
ax.bar([-0.125, 1.0-0.125], [0, 100], 0.25)
38+
ax.spines['right'].set_color('none')
39+
ax.spines['top'].set_color('none')
40+
ax.xaxis.set_ticks_position('bottom')
41+
ax.set_xticks([0, 1])
42+
ax.set_xlim([-0.5, 1.5])
43+
ax.set_ylim([0, 110])
44+
ax.set_xticklabels(['CONFIRMED BY\nEXPERIMENT', 'REFUTED BY\nEXPERIMENT'])
45+
plt.yticks([])
46+
47+
plt.title("CLAIMS OF SUPERNATURAL POWERS")
48+
49+
fig.text(
50+
0.5, 0.05,
51+
'"The Data So Far" from xkcd by Randall Monroe',
52+
ha='center')
53+
54+
plt.show()

lib/matplotlib/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,12 +1052,15 @@ class rc_context(object):
10521052
def __init__(self, rc=None, fname=None):
10531053
self.rcdict = rc
10541054
self.fname = fname
1055-
def __enter__(self):
10561055
self._rcparams = rcParams.copy()
10571056
if self.fname:
10581057
rc_file(self.fname)
10591058
if self.rcdict:
10601059
rcParams.update(self.rcdict)
1060+
1061+
def __enter__(self):
1062+
return self
1063+
10611064
def __exit__(self, type, value, tb):
10621065
rcParams.update(self._rcparams)
10631066

@@ -1190,6 +1193,7 @@ def tk_window_focus():
11901193
'matplotlib.tests.test_mathtext',
11911194
'matplotlib.tests.test_mlab',
11921195
'matplotlib.tests.test_patches',
1196+
'matplotlib.tests.test_patheffects',
11931197
'matplotlib.tests.test_pickle',
11941198
'matplotlib.tests.test_rcparams',
11951199
'matplotlib.tests.test_scale',

lib/matplotlib/artist.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def __init__(self):
101101
self._url = None
102102
self._gid = None
103103
self._snap = None
104+
self._sketch = rcParams['path.sketch']
105+
self._path_effects = rcParams['path.effects']
104106

105107
def __getstate__(self):
106108
d = self.__dict__.copy()
@@ -456,6 +458,63 @@ def set_snap(self, snap):
456458
"""
457459
self._snap = snap
458460

461+
def get_sketch_params(self):
462+
"""
463+
Returns the sketch parameters for the artist.
464+
465+
Returns
466+
-------
467+
sketch_params : tuple or `None`
468+
469+
A 3-tuple with the following elements:
470+
471+
* `scale`: The amplitude of the wiggle perpendicular to the
472+
source line.
473+
474+
* `length`: The length of the wiggle along the line.
475+
476+
* `randomness`: The scale factor by which the length is
477+
shrunken or expanded.
478+
479+
May return `None` if no sketch parameters were set.
480+
"""
481+
return self._sketch
482+
483+
def set_sketch_params(self, scale=None, length=None, randomness=None):
484+
"""
485+
Sets the the sketch parameters.
486+
487+
Parameters
488+
----------
489+
490+
scale : float, optional
491+
The amplitude of the wiggle perpendicular to the source
492+
line, in pixels. If scale is `None`, or not provided, no
493+
sketch filter will be provided.
494+
495+
length : float, optional
496+
The length of the wiggle along the line, in pixels
497+
(default 128.0)
498+
499+
randomness : float, optional
500+
The scale factor by which the length is shrunken or
501+
expanded (default 16.0)
502+
"""
503+
if scale is None:
504+
self._sketch = None
505+
else:
506+
self._sketch = (scale, length or 128.0, randomness or 16.0)
507+
508+
def set_path_effects(self, path_effects):
509+
"""
510+
set path_effects, which should be a list of instances of
511+
matplotlib.patheffect._Base class or its derivatives.
512+
"""
513+
self._path_effects = path_effects
514+
515+
def get_path_effects(self):
516+
return self._path_effects
517+
459518
def get_figure(self):
460519
"""
461520
Return the :class:`~matplotlib.figure.Figure` instance the
@@ -672,7 +731,7 @@ def update(self, props):
672731
store = self.eventson
673732
self.eventson = False
674733
changed = False
675-
734+
676735
for k, v in props.iteritems():
677736
func = getattr(self, 'set_' + k, None)
678737
if func is None or not callable(func):
@@ -728,6 +787,8 @@ def update_from(self, other):
728787
self._clippath = other._clippath
729788
self._lod = other._lod
730789
self._label = other._label
790+
self._sketch = other._sketch
791+
self._path_effects = other._path_effects
731792
self.pchanged()
732793

733794
def properties(self):

lib/matplotlib/backend_bases.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,7 @@ def __init__(self):
701701
self._url = None
702702
self._gid = None
703703
self._snap = None
704+
self._sketch = None
704705

705706
def copy_properties(self, gc):
706707
'Copy properties from gc to self'
@@ -720,6 +721,7 @@ def copy_properties(self, gc):
720721
self._url = gc._url
721722
self._gid = gc._gid
722723
self._snap = gc._snap
724+
self._sketch = gc._sketch
723725

724726
def restore(self):
725727
"""
@@ -1003,6 +1005,53 @@ def get_hatch_path(self, density=6.0):
10031005
return None
10041006
return Path.hatch(self._hatch, density)
10051007

1008+
def get_sketch_params(self):
1009+
"""
1010+
Returns the sketch parameters for the artist.
1011+
1012+
Returns
1013+
-------
1014+
sketch_params : tuple or `None`
1015+
1016+
A 3-tuple with the following elements:
1017+
1018+
* `scale`: The amplitude of the wiggle perpendicular to the
1019+
source line.
1020+
1021+
* `length`: The length of the wiggle along the line.
1022+
1023+
* `randomness`: The scale factor by which the length is
1024+
shrunken or expanded.
1025+
1026+
May return `None` if no sketch parameters were set.
1027+
"""
1028+
return self._sketch
1029+
1030+
def set_sketch_params(self, scale=None, length=None, randomness=None):
1031+
"""
1032+
Sets the the sketch parameters.
1033+
1034+
Parameters
1035+
----------
1036+
1037+
scale : float, optional
1038+
The amplitude of the wiggle perpendicular to the source
1039+
line, in pixels. If scale is `None`, or not provided, no
1040+
sketch filter will be provided.
1041+
1042+
length : float, optional
1043+
The length of the wiggle along the line, in pixels
1044+
(default 128.0)
1045+
1046+
randomness : float, optional
1047+
The scale factor by which the length is shrunken or
1048+
expanded (default 16.0)
1049+
"""
1050+
if scale is None:
1051+
self._sketch = None
1052+
else:
1053+
self._sketch = (scale, length or 128.0, randomness or 16.0)
1054+
10061055

10071056
class TimerBase(object):
10081057
'''
@@ -1937,7 +1986,7 @@ def print_jpg(self, filename_or_obj, *args, **kwargs):
19371986
19381987
*quality*: The image quality, on a scale from 1 (worst) to
19391988
95 (best). The default is 95, if not given in the
1940-
matplotlibrc file in the savefig.jpeg_quality parameter.
1989+
matplotlibrc file in the savefig.jpeg_quality parameter.
19411990
Values above 95 should be avoided; 100 completely
19421991
disables the JPEG quantization stage.
19431992
@@ -1957,7 +2006,7 @@ def print_jpg(self, filename_or_obj, *args, **kwargs):
19572006
options = cbook.restrict_dict(kwargs, ['quality', 'optimize',
19582007
'progressive'])
19592008

1960-
if 'quality' not in options:
2009+
if 'quality' not in options:
19612010
options['quality'] = rcParams['savefig.jpeg_quality']
19622011

19632012
return image.save(filename_or_obj, format='jpeg', **options)

0 commit comments

Comments
 (0)