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

Skip to content

Commit 35ab43a

Browse files
Marin GillesMrngilles
Marin Gilles
authored andcommitted
Adding the style rc parameter
Allows to support cascading stylesheets directly from the matplorlibrc fixed an issue with depth > 1 added test getting parent styles + sample test styles fixed location of sample styles PEP8 fixes added import of stylesheets in setupext and test_only fixed stylesheet path in test_style other styles path fix other fix for styles path fixed an issue in style.use priority of styles passed added smaller functions for get_parents_styles function Fixed issues concerning parent styles importing other small functions to insert parent styles in the style list + small tweaks simplified the check of dictionnary like object using isinstance Few changes (some refactoring + docstring change) Using @tonysyu dictonnary flattening function to get children styles Added tests + dictionnary flattening fixes Docstring addition PEP8 fix pep8 fix removed style file imports needed for the now removed tests Fix for Py 2.6: some nose function did not exist
1 parent e12d103 commit 35ab43a

File tree

4 files changed

+242
-25
lines changed

4 files changed

+242
-25
lines changed

lib/matplotlib/rcsetup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,8 @@ def validate_animation_writer_path(p):
826826

827827
# a map from key -> value, converter
828828
defaultParams = {
829+
'style': [[''], validate_stringlist],
830+
829831
'backend': ['Agg', validate_backend], # agg is certainly
830832
# present
831833
'backend_fallback': [True, validate_bool], # agg is certainly present

lib/matplotlib/style/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
from __future__ import absolute_import
22

33
from .core import use, context, available, library, reload_library
4+
from matplotlib import rcParams
5+
if rcParams['style']:
6+
use(rcParams['style'])

lib/matplotlib/style/core.py

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""
1818
import os
1919
import re
20+
import sys
2021
import contextlib
2122
import warnings
2223

@@ -33,7 +34,7 @@
3334
USER_LIBRARY_PATHS = [os.path.join(mpl._get_configdir(), 'stylelib')]
3435
STYLE_EXTENSION = 'mplstyle'
3536
STYLE_FILE_PATTERN = re.compile('([\S]+).%s$' % STYLE_EXTENSION)
36-
37+
PARENT_STYLES = 'style'
3738

3839
# A list of rcParams that should not be applied from styles
3940
STYLE_BLACKLIST = set([
@@ -65,7 +66,7 @@ def _apply_style(d, warn=True):
6566
mpl.rcParams.update(_remove_blacklisted_style_params(d, warn=warn))
6667

6768

68-
def use(style):
69+
def use(styles):
6970
"""Use matplotlib style settings from a style specification.
7071
7172
The style name of 'default' is reserved for reverting back to
@@ -89,28 +90,93 @@ def use(style):
8990
9091
9192
"""
92-
if cbook.is_string_like(style) or hasattr(style, 'keys'):
93+
if cbook.is_string_like(styles) or hasattr(styles, 'keys'):
9394
# If name is a single str or dict, make it a single element list.
94-
styles = [style]
95-
else:
96-
styles = style
97-
98-
for style in styles:
99-
if not cbook.is_string_like(style):
100-
_apply_style(style)
101-
elif style == 'default':
102-
_apply_style(rcParamsDefault, warn=False)
103-
elif style in library:
104-
_apply_style(library[style])
95+
styles = [styles]
96+
flattened_style = _flatten_style_dict({PARENT_STYLES: styles})
97+
_apply_style(flattened_style)
98+
99+
100+
def _expand_parent(parent_style):
101+
if cbook.is_string_like(parent_style):
102+
if parent_style == "default":
103+
parent_style = rcParamsDefault
105104
else:
106-
try:
107-
rc = rc_params_from_file(style, use_default_template=False)
108-
_apply_style(rc)
109-
except IOError:
110-
msg = ("'%s' not found in the style library and input is "
111-
"not a valid URL or path. See `style.available` for "
112-
"list of available styles.")
113-
raise IOError(msg % style)
105+
parent_style = get_style_dict(parent_style)
106+
return parent_style
107+
108+
109+
def flatten_inheritance_dict(child_dict, parent_key,
110+
expand_parent=lambda x: x):
111+
"""Return a flattened version of dictionary that inherits from a parent.
112+
113+
Parameters
114+
----------
115+
child_dict : dict
116+
Dictionary with a special key that points to a dictionary of defaults,
117+
or a value that can be expanded to a dictionary of defaults.
118+
parent_key : str
119+
The key that points to a list of parents.
120+
expand_parent : callable(parent) -> dict
121+
Function that returns a dictionary from the value corresponding to
122+
`parent_key`. By default, this simply returns the value.
123+
"""
124+
if parent_key not in child_dict:
125+
return child_dict.copy()
126+
127+
parents = child_dict[parent_key]
128+
if isinstance(parents, dict):
129+
parents = [parents]
130+
if not isinstance(parents, (list, tuple)):
131+
msg = "Parent value must be list or tuple, but given {!r}"
132+
raise ValueError(msg.format(parents))
133+
134+
# Expand any parents defined by `child_dict` into dictionaries.
135+
parents = (expand_parent(p) for p in parents)
136+
137+
# Resolve any grand-parents defined by parents of `child_dict`
138+
parents = [flatten_inheritance_dict(p, parent_key, expand_parent)
139+
for p in parents]
140+
141+
# Child will override parent values in `dict.update` so put it last.
142+
ordered_dicts = parents + [child_dict]
143+
144+
# Copy first dictionary and update with subsequent dictionaries.
145+
output_dict = ordered_dicts[0].copy()
146+
for d in ordered_dicts[1:]:
147+
output_dict.update(d)
148+
149+
# Since the parent data been resolved, remove parent references.
150+
del output_dict[parent_key]
151+
return output_dict
152+
153+
154+
def _flatten_style_dict(style_dict):
155+
return flatten_inheritance_dict(style_dict, PARENT_STYLES,
156+
expand_parent=_expand_parent)
157+
158+
159+
def get_style_dict(style):
160+
"""Returns a dictionnary containing all the parameters from the
161+
style file.
162+
163+
Parameters
164+
----------
165+
style : str
166+
style from the default library, the personal library or any
167+
full path.
168+
"""
169+
if style in library:
170+
return library[style]
171+
else:
172+
try:
173+
return rc_params_from_file(style,
174+
use_default_template=False)
175+
except IOError:
176+
msg = ("'%s' not found in the style library and input is "
177+
"not a valid URL or path. See `style.available` for "
178+
"list of available styles.")
179+
raise IOError(msg % style)
114180

115181

116182
@contextlib.contextmanager

lib/matplotlib/tests/test_style.py

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
from contextlib import contextmanager
99

1010
from nose import SkipTest
11-
from nose.tools import assert_raises
11+
from nose.tools import assert_raises, assert_equal
1212
from nose.plugins.attrib import attr
1313

1414
import matplotlib as mpl
1515
from matplotlib import style
16-
from matplotlib.style.core import USER_LIBRARY_PATHS, STYLE_EXTENSION
16+
from matplotlib.style.core import (USER_LIBRARY_PATHS,
17+
STYLE_EXTENSION,
18+
BASE_LIBRARY_PATH,
19+
flatten_inheritance_dict, get_style_dict)
1720

1821
from matplotlib.externals import six
1922

@@ -25,7 +28,8 @@
2528
@contextmanager
2629
def temp_style(style_name, settings=None):
2730
"""Context manager to create a style sheet in a temporary directory."""
28-
settings = DUMMY_SETTINGS
31+
if not settings:
32+
settings = DUMMY_SETTINGS
2933
temp_file = '%s.%s' % (style_name, STYLE_EXTENSION)
3034

3135
# Write style settings to file in the temp directory.
@@ -137,6 +141,148 @@ def test_context_with_badparam():
137141
assert mpl.rcParams[PARAM] == other_value
138142

139143

144+
def test_get_style_dict():
145+
style_dict = get_style_dict('bmh')
146+
assert(isinstance(style_dict, dict))
147+
148+
149+
def test_get_style_dict_from_lib():
150+
style_dict = get_style_dict('bmh')
151+
assert_equal(style_dict['lines.linewidth'], 2.0)
152+
153+
154+
def test_get_style_dict_from_file():
155+
style_dict = get_style_dict(os.path.join(BASE_LIBRARY_PATH,
156+
'bmh.mplstyle'))
157+
assert_equal(style_dict['lines.linewidth'], 2.0)
158+
159+
160+
def test_parent_stylesheet():
161+
parent_value = 'blue'
162+
parent = {PARAM: parent_value}
163+
child = {'style': parent}
164+
with style.context(child):
165+
assert_equal(mpl.rcParams[PARAM], parent_value)
166+
167+
168+
def test_parent_stylesheet_children_override():
169+
parent_value = 'blue'
170+
child_value = 'gray'
171+
parent = {PARAM: parent_value}
172+
child = {'style': parent, PARAM: child_value}
173+
with style.context(child):
174+
assert_equal(mpl.rcParams[PARAM], child_value)
175+
176+
177+
def test_grandparent_stylesheet():
178+
grandparent_value = 'blue'
179+
grandparent = {PARAM: grandparent_value}
180+
parent = {'style': grandparent}
181+
child = {'style': parent}
182+
with style.context(child):
183+
assert_equal(mpl.rcParams[PARAM], grandparent_value)
184+
185+
186+
def test_parent_stylesheet_from_string():
187+
parent_param = 'lines.linewidth'
188+
parent_value = 2.0
189+
parent = {parent_param: parent_value}
190+
child = {'style': ['parent']}
191+
with temp_style('parent', settings=parent):
192+
with style.context(child):
193+
assert_equal(mpl.rcParams[parent_param], parent_value)
194+
195+
196+
def test_parent_stylesheet_brothers():
197+
parent_param = PARAM
198+
parent_value1 = 'blue'
199+
parent_value2 = 'gray'
200+
parent1 = {parent_param: parent_value1}
201+
parent2 = {parent_param: parent_value2}
202+
child = {'style': [parent1, parent2]}
203+
with style.context(child):
204+
assert_equal(mpl.rcParams[parent_param], parent_value2)
205+
206+
207+
# Dictionnary flattening function tests
208+
def test_empty_dict():
209+
child = {}
210+
flattened = flatten_inheritance_dict(child, 'parents')
211+
assert_equal(flattened, child)
212+
213+
214+
def test_no_parent():
215+
child = {'my-key': 'my-value'}
216+
flattened = flatten_inheritance_dict(child, 'parents')
217+
assert_equal(flattened, child)
218+
# Verify that flatten_inheritance_dict always returns a copy.
219+
assert(flattened is not child)
220+
221+
222+
def test_non_list_raises():
223+
child = {'parents': 'parent-value'}
224+
assert_raises(ValueError, flatten_inheritance_dict, child,
225+
'parents')
226+
227+
228+
def test_child_with_no_unique_values():
229+
parent = {'a': 1}
230+
child = {'parents': [parent]}
231+
flattened = flatten_inheritance_dict(child, 'parents')
232+
assert_equal(flattened, parent)
233+
234+
235+
def test_child_overrides_parent_value():
236+
parent = {'a': 'old-value'}
237+
child = {'parents': [parent], 'a': 'new-value'}
238+
flattened = flatten_inheritance_dict(child, 'parents')
239+
assert_equal(flattened, {'a': 'new-value'})
240+
241+
242+
def test_parents_with_distinct_values():
243+
child = {'parents': [{'a': 1}, {'b': 2}]}
244+
flattened = flatten_inheritance_dict(child, 'parents')
245+
assert_equal(flattened, {'a': 1, 'b': 2})
246+
247+
248+
def test_later_parent_overrides_former():
249+
child = {'parents': [{'a': 1}, {'a': 2}]}
250+
flattened = flatten_inheritance_dict(child, 'parents')
251+
assert_equal(flattened, {'a': 2})
252+
253+
254+
def test_grandparent():
255+
grandparent = {'a': 1}
256+
parent = {'parents': [grandparent]}
257+
child = {'parents': [parent]}
258+
flattened = flatten_inheritance_dict(child, 'parents')
259+
assert_equal(flattened, grandparent)
260+
261+
262+
def test_custom_expand_parent():
263+
parent_map = {'a-pointer': {'a': 1}, 'b-pointer': {'b': 2}}
264+
265+
def expand_parent(key):
266+
return parent_map[key]
267+
268+
child = {'parents': ['a-pointer', 'b-pointer']}
269+
flattened = flatten_inheritance_dict(child, 'parents',
270+
expand_parent=expand_parent)
271+
assert_equal(flattened, {'a': 1, 'b': 2})
272+
273+
274+
def test_circular_parents():
275+
parent_map = {'a-pointer': {'parents': ['b-pointer']},
276+
'b-pointer': {'parents': ['a-pointer']}}
277+
278+
def expand_parent(key):
279+
return parent_map[key]
280+
281+
child = {'parents': ['a-pointer']}
282+
assert_raises(RuntimeError, flatten_inheritance_dict, child,
283+
'parents', expand_parent=expand_parent)
284+
285+
140286
if __name__ == '__main__':
141287
from numpy import testing
142288
testing.run_module_suite()

0 commit comments

Comments
 (0)