Coverage for /usr/lib/python3/dist-packages/matplotlib/style/core.py: 52%
103 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""
2Core functions and attributes for the matplotlib style library:
4``use``
5 Select style sheet to override the current matplotlib settings.
6``context``
7 Context manager to use a style sheet temporarily.
8``available``
9 List available style sheets.
10``library``
11 A dictionary of style names and matplotlib settings.
12"""
14import contextlib
15import logging
16import os
17from pathlib import Path
18import re
19import warnings
21import matplotlib as mpl
22from matplotlib import _api, _docstring, rc_params_from_file, rcParamsDefault
24_log = logging.getLogger(__name__)
26__all__ = ['use', 'context', 'available', 'library', 'reload_library']
29@_api.caching_module_getattr # module-level deprecations
30class __getattr__:
31 STYLE_FILE_PATTERN = _api.deprecated("3.5", obj_type="")(property(
32 lambda self: re.compile(r'([\S]+).%s$' % STYLE_EXTENSION)))
35BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib')
36# Users may want multiple library paths, so store a list of paths.
37USER_LIBRARY_PATHS = [os.path.join(mpl.get_configdir(), 'stylelib')]
38STYLE_EXTENSION = 'mplstyle'
39# A list of rcParams that should not be applied from styles
40STYLE_BLACKLIST = {
41 'interactive', 'backend', 'webagg.port', 'webagg.address',
42 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback',
43 'toolbar', 'timezone', 'figure.max_open_warning',
44 'figure.raise_window', 'savefig.directory', 'tk.window_focus',
45 'docstring.hardcopy', 'date.epoch'}
46_DEPRECATED_SEABORN_STYLES = {
47 s: s.replace("seaborn", "seaborn-v0_8")
48 for s in [
49 "seaborn",
50 "seaborn-bright",
51 "seaborn-colorblind",
52 "seaborn-dark",
53 "seaborn-darkgrid",
54 "seaborn-dark-palette",
55 "seaborn-deep",
56 "seaborn-muted",
57 "seaborn-notebook",
58 "seaborn-paper",
59 "seaborn-pastel",
60 "seaborn-poster",
61 "seaborn-talk",
62 "seaborn-ticks",
63 "seaborn-white",
64 "seaborn-whitegrid",
65 ]
66}
67_DEPRECATED_SEABORN_MSG = (
68 "The seaborn styles shipped by Matplotlib are deprecated since %(since)s, "
69 "as they no longer correspond to the styles shipped by seaborn. However, "
70 "they will remain available as 'seaborn-v0_8-<style>'. Alternatively, "
71 "directly use the seaborn API instead.")
74def _remove_blacklisted_style_params(d, warn=True):
75 o = {}
76 for key in d: # prevent triggering RcParams.__getitem__('backend')
77 if key in STYLE_BLACKLIST:
78 if warn:
79 _api.warn_external(
80 f"Style includes a parameter, {key!r}, that is not "
81 "related to style. Ignoring this parameter.")
82 else:
83 o[key] = d[key]
84 return o
87def _apply_style(d, warn=True):
88 mpl.rcParams.update(_remove_blacklisted_style_params(d, warn=warn))
91@_docstring.Substitution(
92 "\n".join(map("- {}".format, sorted(STYLE_BLACKLIST, key=str.lower)))
93)
94def use(style):
95 """
96 Use Matplotlib style settings from a style specification.
98 The style name of 'default' is reserved for reverting back to
99 the default style settings.
101 .. note::
103 This updates the `.rcParams` with the settings from the style.
104 `.rcParams` not defined in the style are kept.
106 Parameters
107 ----------
108 style : str, dict, Path or list
109 A style specification. Valid options are:
111 +------+-------------------------------------------------------------+
112 | str | The name of a style or a path/URL to a style file. For a |
113 | | list of available style names, see `.style.available`. |
114 +------+-------------------------------------------------------------+
115 | dict | Dictionary with valid key/value pairs for |
116 | | `matplotlib.rcParams`. |
117 +------+-------------------------------------------------------------+
118 | Path | A path-like object which is a path to a style file. |
119 +------+-------------------------------------------------------------+
120 | list | A list of style specifiers (str, Path or dict) applied from |
121 | | first to last in the list. |
122 +------+-------------------------------------------------------------+
124 Notes
125 -----
126 The following `.rcParams` are not related to style and will be ignored if
127 found in a style specification:
129 %s
130 """
131 if isinstance(style, (str, Path)) or hasattr(style, 'keys'):
132 # If name is a single str, Path or dict, make it a single element list.
133 styles = [style]
134 else:
135 styles = style
137 style_alias = {'mpl20': 'default', 'mpl15': 'classic'}
139 def fix_style(s):
140 if isinstance(s, str):
141 s = style_alias.get(s, s)
142 if s in _DEPRECATED_SEABORN_STYLES:
143 _api.warn_deprecated("3.6", message=_DEPRECATED_SEABORN_MSG)
144 s = _DEPRECATED_SEABORN_STYLES[s]
145 return s
147 for style in map(fix_style, styles):
148 if not isinstance(style, (str, Path)):
149 _apply_style(style)
150 elif style == 'default':
151 # Deprecation warnings were already handled when creating
152 # rcParamsDefault, no need to reemit them here.
153 with _api.suppress_matplotlib_deprecation_warning():
154 _apply_style(rcParamsDefault, warn=False)
155 elif style in library:
156 _apply_style(library[style])
157 else:
158 try:
159 rc = rc_params_from_file(style, use_default_template=False)
160 _apply_style(rc)
161 except IOError as err:
162 raise IOError(
163 "{!r} not found in the style library and input is not a "
164 "valid URL or path; see `style.available` for list of "
165 "available styles".format(style)) from err
168@contextlib.contextmanager
169def context(style, after_reset=False):
170 """
171 Context manager for using style settings temporarily.
173 Parameters
174 ----------
175 style : str, dict, Path or list
176 A style specification. Valid options are:
178 +------+-------------------------------------------------------------+
179 | str | The name of a style or a path/URL to a style file. For a |
180 | | list of available style names, see `.style.available`. |
181 +------+-------------------------------------------------------------+
182 | dict | Dictionary with valid key/value pairs for |
183 | | `matplotlib.rcParams`. |
184 +------+-------------------------------------------------------------+
185 | Path | A path-like object which is a path to a style file. |
186 +------+-------------------------------------------------------------+
187 | list | A list of style specifiers (str, Path or dict) applied from |
188 | | first to last in the list. |
189 +------+-------------------------------------------------------------+
191 after_reset : bool
192 If True, apply style after resetting settings to their defaults;
193 otherwise, apply style on top of the current settings.
194 """
195 with mpl.rc_context():
196 if after_reset:
197 mpl.rcdefaults()
198 use(style)
199 yield
202@_api.deprecated("3.5")
203def load_base_library():
204 """Load style library defined in this package."""
205 library = read_style_directory(BASE_LIBRARY_PATH)
206 return library
209@_api.deprecated("3.5")
210def iter_user_libraries():
211 for stylelib_path in USER_LIBRARY_PATHS:
212 stylelib_path = os.path.expanduser(stylelib_path)
213 if os.path.exists(stylelib_path) and os.path.isdir(stylelib_path):
214 yield stylelib_path
217def update_user_library(library):
218 """Update style library with user-defined rc files."""
219 for stylelib_path in map(os.path.expanduser, USER_LIBRARY_PATHS):
220 styles = read_style_directory(stylelib_path)
221 update_nested_dict(library, styles)
222 return library
225def read_style_directory(style_dir):
226 """Return dictionary of styles defined in *style_dir*."""
227 styles = dict()
228 for path in Path(style_dir).glob(f"*.{STYLE_EXTENSION}"):
229 with warnings.catch_warnings(record=True) as warns:
230 styles[path.stem] = rc_params_from_file(
231 path, use_default_template=False)
232 for w in warns:
233 _log.warning('In %s: %s', path, w.message)
234 return styles
237def update_nested_dict(main_dict, new_dict):
238 """
239 Update nested dict (only level of nesting) with new values.
241 Unlike `dict.update`, this assumes that the values of the parent dict are
242 dicts (or dict-like), so you shouldn't replace the nested dict if it
243 already exists. Instead you should update the sub-dict.
244 """
245 # update named styles specified by user
246 for name, rc_dict in new_dict.items():
247 main_dict.setdefault(name, {}).update(rc_dict)
248 return main_dict
251class _StyleLibrary(dict):
252 def __getitem__(self, key):
253 if key in _DEPRECATED_SEABORN_STYLES:
254 _api.warn_deprecated("3.6", message=_DEPRECATED_SEABORN_MSG)
255 key = _DEPRECATED_SEABORN_STYLES[key]
257 return dict.__getitem__(self, key)
260# Load style library
261# ==================
262_base_library = read_style_directory(BASE_LIBRARY_PATH)
263library = _StyleLibrary()
264available = []
267def reload_library():
268 """Reload the style library."""
269 library.clear()
270 library.update(update_user_library(_base_library))
271 available[:] = sorted(library.keys())
274reload_library()