Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/style/core.py: 53%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

81 statements  

1""" 

2Core functions and attributes for the matplotlib style library: 

3 

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""" 

13 

14import contextlib 

15import logging 

16import os 

17from pathlib import Path 

18import sys 

19import warnings 

20 

21if sys.version_info >= (3, 10): 

22 import importlib.resources as importlib_resources 

23else: 

24 # Even though Py3.9 has importlib.resources, it doesn't properly handle 

25 # modules added in sys.path. 

26 import importlib_resources 

27 

28import matplotlib as mpl 

29from matplotlib import _api, _docstring, _rc_params_in_file, rcParamsDefault 

30 

31_log = logging.getLogger(__name__) 

32 

33__all__ = ['use', 'context', 'available', 'library', 'reload_library'] 

34 

35 

36BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib') 

37# Users may want multiple library paths, so store a list of paths. 

38USER_LIBRARY_PATHS = [os.path.join(mpl.get_configdir(), 'stylelib')] 

39STYLE_EXTENSION = 'mplstyle' 

40# A list of rcParams that should not be applied from styles 

41STYLE_BLACKLIST = { 

42 'interactive', 'backend', 'webagg.port', 'webagg.address', 

43 'webagg.port_retries', 'webagg.open_in_browser', 'backend_fallback', 

44 'toolbar', 'timezone', 'figure.max_open_warning', 

45 'figure.raise_window', 'savefig.directory', 'tk.window_focus', 

46 'docstring.hardcopy', 'date.epoch'} 

47 

48 

49@_docstring.Substitution( 

50 "\n".join(map("- {}".format, sorted(STYLE_BLACKLIST, key=str.lower))) 

51) 

52def use(style): 

53 """ 

54 Use Matplotlib style settings from a style specification. 

55 

56 The style name of 'default' is reserved for reverting back to 

57 the default style settings. 

58 

59 .. note:: 

60 

61 This updates the `.rcParams` with the settings from the style. 

62 `.rcParams` not defined in the style are kept. 

63 

64 Parameters 

65 ---------- 

66 style : str, dict, Path or list 

67 

68 A style specification. Valid options are: 

69 

70 str 

71 - One of the style names in `.style.available` (a builtin style or 

72 a style installed in the user library path). 

73 

74 - A dotted name of the form "package.style_name"; in that case, 

75 "package" should be an importable Python package name, e.g. at 

76 ``/path/to/package/__init__.py``; the loaded style file is 

77 ``/path/to/package/style_name.mplstyle``. (Style files in 

78 subpackages are likewise supported.) 

79 

80 - The path or URL to a style file, which gets loaded by 

81 `.rc_params_from_file`. 

82 

83 dict 

84 A mapping of key/value pairs for `matplotlib.rcParams`. 

85 

86 Path 

87 The path to a style file, which gets loaded by 

88 `.rc_params_from_file`. 

89 

90 list 

91 A list of style specifiers (str, Path or dict), which are applied 

92 from first to last in the list. 

93 

94 Notes 

95 ----- 

96 The following `.rcParams` are not related to style and will be ignored if 

97 found in a style specification: 

98 

99 %s 

100 """ 

101 if isinstance(style, (str, Path)) or hasattr(style, 'keys'): 

102 # If name is a single str, Path or dict, make it a single element list. 

103 styles = [style] 

104 else: 

105 styles = style 

106 

107 style_alias = {'mpl20': 'default', 'mpl15': 'classic'} 

108 

109 for style in styles: 

110 if isinstance(style, str): 

111 style = style_alias.get(style, style) 

112 if style == "default": 

113 # Deprecation warnings were already handled when creating 

114 # rcParamsDefault, no need to reemit them here. 

115 with _api.suppress_matplotlib_deprecation_warning(): 

116 # don't trigger RcParams.__getitem__('backend') 

117 style = {k: rcParamsDefault[k] for k in rcParamsDefault 

118 if k not in STYLE_BLACKLIST} 

119 elif style in library: 

120 style = library[style] 

121 elif "." in style: 

122 pkg, _, name = style.rpartition(".") 

123 try: 

124 path = (importlib_resources.files(pkg) 

125 / f"{name}.{STYLE_EXTENSION}") 

126 style = _rc_params_in_file(path) 

127 except (ModuleNotFoundError, OSError, TypeError) as exc: 

128 # There is an ambiguity whether a dotted name refers to a 

129 # package.style_name or to a dotted file path. Currently, 

130 # we silently try the first form and then the second one; 

131 # in the future, we may consider forcing file paths to 

132 # either use Path objects or be prepended with "./" and use 

133 # the slash as marker for file paths. 

134 pass 

135 if isinstance(style, (str, Path)): 

136 try: 

137 style = _rc_params_in_file(style) 

138 except OSError as err: 

139 raise OSError( 

140 f"{style!r} is not a valid package style, path of style " 

141 f"file, URL of style file, or library style name (library " 

142 f"styles are listed in `style.available`)") from err 

143 filtered = {} 

144 for k in style: # don't trigger RcParams.__getitem__('backend') 

145 if k in STYLE_BLACKLIST: 

146 _api.warn_external( 

147 f"Style includes a parameter, {k!r}, that is not " 

148 f"related to style. Ignoring this parameter.") 

149 else: 

150 filtered[k] = style[k] 

151 mpl.rcParams.update(filtered) 

152 

153 

154@contextlib.contextmanager 

155def context(style, after_reset=False): 

156 """ 

157 Context manager for using style settings temporarily. 

158 

159 Parameters 

160 ---------- 

161 style : str, dict, Path or list 

162 A style specification. Valid options are: 

163 

164 str 

165 - One of the style names in `.style.available` (a builtin style or 

166 a style installed in the user library path). 

167 

168 - A dotted name of the form "package.style_name"; in that case, 

169 "package" should be an importable Python package name, e.g. at 

170 ``/path/to/package/__init__.py``; the loaded style file is 

171 ``/path/to/package/style_name.mplstyle``. (Style files in 

172 subpackages are likewise supported.) 

173 

174 - The path or URL to a style file, which gets loaded by 

175 `.rc_params_from_file`. 

176 dict 

177 A mapping of key/value pairs for `matplotlib.rcParams`. 

178 

179 Path 

180 The path to a style file, which gets loaded by 

181 `.rc_params_from_file`. 

182 

183 list 

184 A list of style specifiers (str, Path or dict), which are applied 

185 from first to last in the list. 

186 

187 after_reset : bool 

188 If True, apply style after resetting settings to their defaults; 

189 otherwise, apply style on top of the current settings. 

190 """ 

191 with mpl.rc_context(): 

192 if after_reset: 

193 mpl.rcdefaults() 

194 use(style) 

195 yield 

196 

197 

198def update_user_library(library): 

199 """Update style library with user-defined rc files.""" 

200 for stylelib_path in map(os.path.expanduser, USER_LIBRARY_PATHS): 

201 styles = read_style_directory(stylelib_path) 

202 update_nested_dict(library, styles) 

203 return library 

204 

205 

206def read_style_directory(style_dir): 

207 """Return dictionary of styles defined in *style_dir*.""" 

208 styles = dict() 

209 for path in Path(style_dir).glob(f"*.{STYLE_EXTENSION}"): 

210 with warnings.catch_warnings(record=True) as warns: 

211 styles[path.stem] = _rc_params_in_file(path) 

212 for w in warns: 

213 _log.warning('In %s: %s', path, w.message) 

214 return styles 

215 

216 

217def update_nested_dict(main_dict, new_dict): 

218 """ 

219 Update nested dict (only level of nesting) with new values. 

220 

221 Unlike `dict.update`, this assumes that the values of the parent dict are 

222 dicts (or dict-like), so you shouldn't replace the nested dict if it 

223 already exists. Instead you should update the sub-dict. 

224 """ 

225 # update named styles specified by user 

226 for name, rc_dict in new_dict.items(): 

227 main_dict.setdefault(name, {}).update(rc_dict) 

228 return main_dict 

229 

230 

231# Load style library 

232# ================== 

233_base_library = read_style_directory(BASE_LIBRARY_PATH) 

234library = {} 

235available = [] 

236 

237 

238def reload_library(): 

239 """Reload the style library.""" 

240 library.clear() 

241 library.update(update_user_library(_base_library)) 

242 available[:] = sorted(library.keys()) 

243 

244 

245reload_library()