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

Skip to content

Commit 2175cb0

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 3235300 commit 2175cb0

File tree

4 files changed

+243
-25
lines changed

4 files changed

+243
-25
lines changed

lib/matplotlib/rcsetup.py

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

887887
# a map from key -> value, converter
888888
defaultParams = {
889+
'style': [[''], validate_stringlist],
890+
889891
'backend': ['Agg', validate_backend], # agg is certainly
890892
# present
891893
'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 = {
@@ -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: 150 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@
77
from collections import OrderedDict
88
from contextlib import contextmanager
99

10-
from nose.tools import assert_raises
10+
from nose import SkipTest
11+
from nose.tools import assert_raises, assert_equal
1112
from nose.plugins.attrib import attr
1213

1314
import matplotlib as mpl
1415
from matplotlib import style
15-
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)
1620

1721
import six
1822

@@ -24,7 +28,8 @@
2428
@contextmanager
2529
def temp_style(style_name, settings=None):
2630
"""Context manager to create a style sheet in a temporary directory."""
27-
settings = DUMMY_SETTINGS
31+
if not settings:
32+
settings = DUMMY_SETTINGS
2833
temp_file = '%s.%s' % (style_name, STYLE_EXTENSION)
2934

3035
# Write style settings to file in the temp directory.
@@ -130,6 +135,148 @@ def test_context_with_badparam():
130135
assert mpl.rcParams[PARAM] == other_value
131136

132137

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

0 commit comments

Comments
 (0)