From 2175cb07b6660e61075eaa97e2317fec11fbe1eb Mon Sep 17 00:00:00 2001 From: Marin Gilles Date: Tue, 17 Mar 2015 13:41:25 +0100 Subject: [PATCH 1/2] 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 --- lib/matplotlib/rcsetup.py | 2 + lib/matplotlib/style/__init__.py | 3 + lib/matplotlib/style/core.py | 110 ++++++++++++++++----- lib/matplotlib/tests/test_style.py | 153 ++++++++++++++++++++++++++++- 4 files changed, 243 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index b19d234ffa5d..566c3f667b49 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -886,6 +886,8 @@ def validate_animation_writer_path(p): # a map from key -> value, converter defaultParams = { + 'style': [[''], validate_stringlist], + 'backend': ['Agg', validate_backend], # agg is certainly # present 'backend_fallback': [True, validate_bool], # agg is certainly present diff --git a/lib/matplotlib/style/__init__.py b/lib/matplotlib/style/__init__.py index cb0592f41e78..f30f4dd64d96 100644 --- a/lib/matplotlib/style/__init__.py +++ b/lib/matplotlib/style/__init__.py @@ -1,3 +1,6 @@ from __future__ import absolute_import from .core import use, context, available, library, reload_library +from matplotlib import rcParams +if rcParams['style']: + use(rcParams['style']) diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 098f45d5b426..54fa6df4ac4b 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -17,6 +17,7 @@ """ import os import re +import sys import contextlib import warnings @@ -33,7 +34,7 @@ USER_LIBRARY_PATHS = [os.path.join(mpl._get_configdir(), 'stylelib')] STYLE_EXTENSION = 'mplstyle' STYLE_FILE_PATTERN = re.compile('([\S]+).%s$' % STYLE_EXTENSION) - +PARENT_STYLES = 'style' # A list of rcParams that should not be applied from styles STYLE_BLACKLIST = { @@ -65,7 +66,7 @@ def _apply_style(d, warn=True): mpl.rcParams.update(_remove_blacklisted_style_params(d, warn=warn)) -def use(style): +def use(styles): """Use matplotlib style settings from a style specification. The style name of 'default' is reserved for reverting back to @@ -89,28 +90,93 @@ def use(style): """ - if cbook.is_string_like(style) or hasattr(style, 'keys'): + if cbook.is_string_like(styles) or hasattr(styles, 'keys'): # If name is a single str or dict, make it a single element list. - styles = [style] - else: - styles = style - - for style in styles: - if not cbook.is_string_like(style): - _apply_style(style) - elif style == 'default': - _apply_style(rcParamsDefault, warn=False) - elif style in library: - _apply_style(library[style]) + styles = [styles] + flattened_style = _flatten_style_dict({PARENT_STYLES: styles}) + _apply_style(flattened_style) + + +def _expand_parent(parent_style): + if cbook.is_string_like(parent_style): + if parent_style == "default": + parent_style = rcParamsDefault else: - try: - rc = rc_params_from_file(style, use_default_template=False) - _apply_style(rc) - except IOError: - msg = ("'%s' not found in the style library and input is " - "not a valid URL or path. See `style.available` for " - "list of available styles.") - raise IOError(msg % style) + parent_style = get_style_dict(parent_style) + return parent_style + + +def flatten_inheritance_dict(child_dict, parent_key, + expand_parent=lambda x: x): + """Return a flattened version of dictionary that inherits from a parent. + + Parameters + ---------- + child_dict : dict + Dictionary with a special key that points to a dictionary of defaults, + or a value that can be expanded to a dictionary of defaults. + parent_key : str + The key that points to a list of parents. + expand_parent : callable(parent) -> dict + Function that returns a dictionary from the value corresponding to + `parent_key`. By default, this simply returns the value. + """ + if parent_key not in child_dict: + return child_dict.copy() + + parents = child_dict[parent_key] + if isinstance(parents, dict): + parents = [parents] + if not isinstance(parents, (list, tuple)): + msg = "Parent value must be list or tuple, but given {!r}" + raise ValueError(msg.format(parents)) + + # Expand any parents defined by `child_dict` into dictionaries. + parents = (expand_parent(p) for p in parents) + + # Resolve any grand-parents defined by parents of `child_dict` + parents = [flatten_inheritance_dict(p, parent_key, expand_parent) + for p in parents] + + # Child will override parent values in `dict.update` so put it last. + ordered_dicts = parents + [child_dict] + + # Copy first dictionary and update with subsequent dictionaries. + output_dict = ordered_dicts[0].copy() + for d in ordered_dicts[1:]: + output_dict.update(d) + + # Since the parent data been resolved, remove parent references. + del output_dict[parent_key] + return output_dict + + +def _flatten_style_dict(style_dict): + return flatten_inheritance_dict(style_dict, PARENT_STYLES, + expand_parent=_expand_parent) + + +def get_style_dict(style): + """Returns a dictionnary containing all the parameters from the + style file. + + Parameters + ---------- + style : str + style from the default library, the personal library or any + full path. + """ + if style in library: + return library[style] + else: + try: + return rc_params_from_file(style, + use_default_template=False) + except IOError: + msg = ("'%s' not found in the style library and input is " + "not a valid URL or path. See `style.available` for " + "list of available styles.") + raise IOError(msg % style) @contextlib.contextmanager diff --git a/lib/matplotlib/tests/test_style.py b/lib/matplotlib/tests/test_style.py index 901218cb76cf..cdfa87205558 100644 --- a/lib/matplotlib/tests/test_style.py +++ b/lib/matplotlib/tests/test_style.py @@ -7,12 +7,16 @@ from collections import OrderedDict from contextlib import contextmanager -from nose.tools import assert_raises +from nose import SkipTest +from nose.tools import assert_raises, assert_equal from nose.plugins.attrib import attr import matplotlib as mpl from matplotlib import style -from matplotlib.style.core import USER_LIBRARY_PATHS, STYLE_EXTENSION +from matplotlib.style.core import (USER_LIBRARY_PATHS, + STYLE_EXTENSION, + BASE_LIBRARY_PATH, + flatten_inheritance_dict, get_style_dict) import six @@ -24,7 +28,8 @@ @contextmanager def temp_style(style_name, settings=None): """Context manager to create a style sheet in a temporary directory.""" - settings = DUMMY_SETTINGS + if not settings: + settings = DUMMY_SETTINGS temp_file = '%s.%s' % (style_name, STYLE_EXTENSION) # Write style settings to file in the temp directory. @@ -130,6 +135,148 @@ def test_context_with_badparam(): assert mpl.rcParams[PARAM] == other_value +def test_get_style_dict(): + style_dict = get_style_dict('bmh') + assert(isinstance(style_dict, dict)) + + +def test_get_style_dict_from_lib(): + style_dict = get_style_dict('bmh') + assert_equal(style_dict['lines.linewidth'], 2.0) + + +def test_get_style_dict_from_file(): + style_dict = get_style_dict(os.path.join(BASE_LIBRARY_PATH, + 'bmh.mplstyle')) + assert_equal(style_dict['lines.linewidth'], 2.0) + + +def test_parent_stylesheet(): + parent_value = 'blue' + parent = {PARAM: parent_value} + child = {'style': parent} + with style.context(child): + assert_equal(mpl.rcParams[PARAM], parent_value) + + +def test_parent_stylesheet_children_override(): + parent_value = 'blue' + child_value = 'gray' + parent = {PARAM: parent_value} + child = {'style': parent, PARAM: child_value} + with style.context(child): + assert_equal(mpl.rcParams[PARAM], child_value) + + +def test_grandparent_stylesheet(): + grandparent_value = 'blue' + grandparent = {PARAM: grandparent_value} + parent = {'style': grandparent} + child = {'style': parent} + with style.context(child): + assert_equal(mpl.rcParams[PARAM], grandparent_value) + + +def test_parent_stylesheet_from_string(): + parent_param = 'lines.linewidth' + parent_value = 2.0 + parent = {parent_param: parent_value} + child = {'style': ['parent']} + with temp_style('parent', settings=parent): + with style.context(child): + assert_equal(mpl.rcParams[parent_param], parent_value) + + +def test_parent_stylesheet_brothers(): + parent_param = PARAM + parent_value1 = 'blue' + parent_value2 = 'gray' + parent1 = {parent_param: parent_value1} + parent2 = {parent_param: parent_value2} + child = {'style': [parent1, parent2]} + with style.context(child): + assert_equal(mpl.rcParams[parent_param], parent_value2) + + +# Dictionnary flattening function tests +def test_empty_dict(): + child = {} + flattened = flatten_inheritance_dict(child, 'parents') + assert_equal(flattened, child) + + +def test_no_parent(): + child = {'my-key': 'my-value'} + flattened = flatten_inheritance_dict(child, 'parents') + assert_equal(flattened, child) + # Verify that flatten_inheritance_dict always returns a copy. + assert(flattened is not child) + + +def test_non_list_raises(): + child = {'parents': 'parent-value'} + assert_raises(ValueError, flatten_inheritance_dict, child, + 'parents') + + +def test_child_with_no_unique_values(): + parent = {'a': 1} + child = {'parents': [parent]} + flattened = flatten_inheritance_dict(child, 'parents') + assert_equal(flattened, parent) + + +def test_child_overrides_parent_value(): + parent = {'a': 'old-value'} + child = {'parents': [parent], 'a': 'new-value'} + flattened = flatten_inheritance_dict(child, 'parents') + assert_equal(flattened, {'a': 'new-value'}) + + +def test_parents_with_distinct_values(): + child = {'parents': [{'a': 1}, {'b': 2}]} + flattened = flatten_inheritance_dict(child, 'parents') + assert_equal(flattened, {'a': 1, 'b': 2}) + + +def test_later_parent_overrides_former(): + child = {'parents': [{'a': 1}, {'a': 2}]} + flattened = flatten_inheritance_dict(child, 'parents') + assert_equal(flattened, {'a': 2}) + + +def test_grandparent(): + grandparent = {'a': 1} + parent = {'parents': [grandparent]} + child = {'parents': [parent]} + flattened = flatten_inheritance_dict(child, 'parents') + assert_equal(flattened, grandparent) + + +def test_custom_expand_parent(): + parent_map = {'a-pointer': {'a': 1}, 'b-pointer': {'b': 2}} + + def expand_parent(key): + return parent_map[key] + + child = {'parents': ['a-pointer', 'b-pointer']} + flattened = flatten_inheritance_dict(child, 'parents', + expand_parent=expand_parent) + assert_equal(flattened, {'a': 1, 'b': 2}) + + +def test_circular_parents(): + parent_map = {'a-pointer': {'parents': ['b-pointer']}, + 'b-pointer': {'parents': ['a-pointer']}} + + def expand_parent(key): + return parent_map[key] + + child = {'parents': ['a-pointer']} + assert_raises(RuntimeError, flatten_inheritance_dict, child, + 'parents', expand_parent=expand_parent) + + if __name__ == '__main__': from numpy import testing testing.run_module_suite() From 105c80a99f28a90bd02973a3ffc54b535dfb4a95 Mon Sep 17 00:00:00 2001 From: Marin Gilles Date: Tue, 22 Mar 2016 20:01:08 +0100 Subject: [PATCH 2/2] API consistency Fix --- lib/matplotlib/style/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/style/core.py b/lib/matplotlib/style/core.py index 54fa6df4ac4b..cf0e67eacfb1 100644 --- a/lib/matplotlib/style/core.py +++ b/lib/matplotlib/style/core.py @@ -66,7 +66,7 @@ def _apply_style(d, warn=True): mpl.rcParams.update(_remove_blacklisted_style_params(d, warn=warn)) -def use(styles): +def use(style): """Use matplotlib style settings from a style specification. The style name of 'default' is reserved for reverting back to @@ -90,10 +90,10 @@ def use(styles): """ - if cbook.is_string_like(styles) or hasattr(styles, 'keys'): + if cbook.is_string_like(style) or hasattr(style, 'keys'): # If name is a single str or dict, make it a single element list. - styles = [styles] - flattened_style = _flatten_style_dict({PARENT_STYLES: styles}) + style = [style] + flattened_style = _flatten_style_dict({PARENT_STYLES: style}) _apply_style(flattened_style) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy