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

Skip to content

Commit e4730c2

Browse files
committed
xkcd.mplstyle w/ quasi parsing of patheffects.{functions} and
new validate_anydict method in rcsetup
1 parent 2cbdff8 commit e4730c2

File tree

8 files changed

+97
-25
lines changed

8 files changed

+97
-25
lines changed

lib/matplotlib/artist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def __init__(self):
209209
self._gid = None
210210
self._snap = None
211211
self._sketch = mpl.rcParams['path.sketch']
212-
self._path_effects = mpl.rcParams['path.effects']
212+
self._path_effects = []
213213
self._sticky_edges = _XYPair([], [])
214214
self._in_layout = True
215215

lib/matplotlib/mpl-data/matplotlibrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@
678678
# - *randomness* is the factor by which the length is
679679
# randomly scaled.
680680
#path.effects:
681-
681+
#path.effects.withStroke: None # {"argument":value } passed to withStroke
682682

683683
## ***************************************************************************
684684
## * SAVING FIGURES *
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## default xkcd style
2+
3+
# line
4+
lines.linewidth : 2.0
5+
6+
# font
7+
font.family : xkcd, xkcd Script, Humor Sans, Comic Neue, Comic Sans MS
8+
font.size : 14.0
9+
10+
# axes
11+
axes.linewidth : 1.5
12+
axes.grid : False
13+
axes.unicode_minus: False
14+
axes.edgecolor: black
15+
16+
# ticks
17+
xtick.major.size : 8
18+
xtick.major.width: 3
19+
ytick.major.size : 8
20+
ytick.major.width: 3
21+
22+
# grids
23+
grid.linewidth: 0.0
24+
25+
# figure
26+
figure.facecolor: white
27+
28+
# path
29+
path.sketch : (1, 100, 2)
30+
path.effects.withStroke : {"linewidth": 4, "foreground": 'w' }

lib/matplotlib/patheffects.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
.. seealso::
66
:ref:`patheffects_guide`
77
"""
8+
import sys
89

10+
import matplotlib as mpl
911
from matplotlib.backend_bases import RendererBase
12+
import matplotlib.cbook as cbook
1013
from matplotlib import colors as mcolors
1114
from matplotlib import patches as mpatches
1215
from matplotlib import transforms as mtransforms
@@ -90,7 +93,13 @@ def __init__(self, path_effects, renderer):
9093
renderer : `~matplotlib.backend_bases.RendererBase` subclass
9194
9295
"""
93-
self._path_effects = path_effects
96+
compound_path_effects = mpl.rcParams['path.effects']
97+
for k, v in mpl.rcParams.find_all('path.effects.*').copy().items():
98+
if path_effect_name := k.lstrip('path.effects') and v is not None:
99+
path_effect_function = sys.modules[__name__].__dict__[path_effect_name]
100+
compound_path_effects.append(path_effect_function(**v))
101+
compound_path_effects.append(path_effects)
102+
self._path_effects = cbook.flatten(compound_path_effects)
94103
self._renderer = renderer
95104

96105
def copy_with_path_effect(self, path_effects):

lib/matplotlib/pyplot.py

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -748,27 +748,8 @@ def xkcd(
748748
stack = ExitStack()
749749
stack.callback(dict.update, rcParams, rcParams.copy()) # type: ignore
750750

751-
from matplotlib import patheffects
752-
rcParams.update({
753-
'font.family': ['xkcd', 'xkcd Script', 'Humor Sans', 'Comic Neue',
754-
'Comic Sans MS'],
755-
'font.size': 14.0,
756-
'path.sketch': (scale, length, randomness),
757-
'path.effects': [
758-
patheffects.withStroke(linewidth=4, foreground="w")],
759-
'axes.linewidth': 1.5,
760-
'lines.linewidth': 2.0,
761-
'figure.facecolor': 'white',
762-
'grid.linewidth': 0.0,
763-
'axes.grid': False,
764-
'axes.unicode_minus': False,
765-
'axes.edgecolor': 'black',
766-
'xtick.major.size': 8,
767-
'xtick.major.width': 3,
768-
'ytick.major.size': 8,
769-
'ytick.major.width': 3,
770-
})
771-
751+
rcParams.update({**style.library["xkcd"],
752+
'path.sketch': (scale, length, randomness)})
772753
return stack
773754

774755

lib/matplotlib/rcsetup.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,19 @@ def validate_hist_bins(s):
871871
" a sequence of floats")
872872

873873

874+
def validate_anydict(allow_none=True):
875+
def _validate_dict(d):
876+
try:
877+
d = ast.literal_eval(d)
878+
except ValueError:
879+
pass
880+
if isinstance(d, dict) or (allow_none and (d is None)):
881+
return d
882+
raise ValueError(f"Input {d!r} must be a dictionary {{'k': v}} "
883+
f"{'or None' if allow_none else ''}")
884+
return _validate_dict
885+
886+
874887
class _ignorecase(list):
875888
"""A marker class indicating that a list-of-str is case-insensitive."""
876889

@@ -1291,6 +1304,7 @@ def _convert_validator_spec(key, conv):
12911304
"path.snap": validate_bool,
12921305
"path.sketch": validate_sketch,
12931306
"path.effects": validate_anylist,
1307+
"path.effects.withStroke": validate_anydict(True),
12941308
"agg.path.chunksize": validate_int, # 0 to disable chunking
12951309

12961310
# key-mappings (multi-character mappings should be a list/tuple)

lib/matplotlib/rcsetup.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class ValidateInStrings:
2828

2929
def validate_any(s: Any) -> Any: ...
3030
def validate_anylist(s: Any) -> list[Any]: ...
31+
def validate_anydict(allow_none: bool) -> Callable[[dict[str, Any]|None], dict[str, Any]]: ...
3132
def validate_bool(b: Any) -> bool: ...
3233
def validate_axisbelow(s: Any) -> bool | Literal["line"]: ...
3334
def validate_dpi(s: Any) -> Literal["figure"] | float: ...

lib/matplotlib/tests/test_rcparams.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from matplotlib import _api, _c_internal_utils
1313
import matplotlib.pyplot as plt
1414
import matplotlib.colors as mcolors
15+
import matplotlib.patheffects as path_effects
16+
from matplotlib.testing.decorators import check_figures_equal
17+
1518
import numpy as np
1619
from matplotlib.rcsetup import (
1720
validate_bool,
@@ -28,7 +31,9 @@
2831
validate_markevery,
2932
validate_stringlist,
3033
_validate_linestyle,
31-
_listify_validator)
34+
validate_anydict,
35+
_listify_validator,
36+
)
3237

3338

3439
def test_rcparams(tmpdir):
@@ -628,3 +633,35 @@ def test_rcparams_legend_loc_from_file(tmpdir, value):
628633

629634
with mpl.rc_context(fname=rc_path):
630635
assert mpl.rcParams["legend.loc"] == value
636+
637+
638+
@pytest.mark.parametrize("allow_none", [True, False])
639+
def test_validate_dict(allow_none):
640+
fval = validate_anydict(allow_none)
641+
assert fval("{'a':1, 'b':2}") == {'a': 1, 'b': 2}
642+
with pytest.raises(ValueError, match=r"Input \['a', 'b'\] "):
643+
fval(['a', 'b'])
644+
645+
646+
def test_validate_dict_none():
647+
assert validate_anydict()(None) is None
648+
with pytest.raises(ValueError,
649+
match=r"Input None must be a dictionary "):
650+
validate_anydict(False)(None)
651+
with pytest.raises(ValueError,
652+
match=r"Input 0 must be a dictionary {'k': v} or None"):
653+
validate_anydict(True)(0)
654+
655+
656+
def test_path_effects():
657+
mpl.rcParams['path.effects.withStroke'] = {'linewidth': 4}
658+
assert mpl.rcParams['path.effects.withStroke'] == {'linewidth': 4}
659+
660+
661+
@check_figures_equal()
662+
def test_path_effects_specific(fig_test, fig_ref):
663+
with mpl.rc_context({'path.effects.withStroke': {'linewidth': 4}}):
664+
fig_test.subplots().plot([1, 2, 3])
665+
666+
with mpl.rc_context({'path.effects': [path_effects.withStroke(linewidth=4)]}):
667+
fig_ref.subplots().plot([1, 2, 3])

0 commit comments

Comments
 (0)