Coverage for /usr/lib/python3/dist-packages/matplotlib/style/core.py: 52%

103 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

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 re 

19import warnings 

20 

21import matplotlib as mpl 

22from matplotlib import _api, _docstring, rc_params_from_file, rcParamsDefault 

23 

24_log = logging.getLogger(__name__) 

25 

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

27 

28 

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

33 

34 

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

72 

73 

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 

85 

86 

87def _apply_style(d, warn=True): 

88 mpl.rcParams.update(_remove_blacklisted_style_params(d, warn=warn)) 

89 

90 

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. 

97 

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

99 the default style settings. 

100 

101 .. note:: 

102 

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

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

105 

106 Parameters 

107 ---------- 

108 style : str, dict, Path or list 

109 A style specification. Valid options are: 

110 

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

123 

124 Notes 

125 ----- 

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

127 found in a style specification: 

128 

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 

136 

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

138 

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 

146 

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 

166 

167 

168@contextlib.contextmanager 

169def context(style, after_reset=False): 

170 """ 

171 Context manager for using style settings temporarily. 

172 

173 Parameters 

174 ---------- 

175 style : str, dict, Path or list 

176 A style specification. Valid options are: 

177 

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

190 

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 

200 

201 

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 

207 

208 

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 

215 

216 

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 

223 

224 

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 

235 

236 

237def update_nested_dict(main_dict, new_dict): 

238 """ 

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

240 

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 

249 

250 

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] 

256 

257 return dict.__getitem__(self, key) 

258 

259 

260# Load style library 

261# ================== 

262_base_library = read_style_directory(BASE_LIBRARY_PATH) 

263library = _StyleLibrary() 

264available = [] 

265 

266 

267def reload_library(): 

268 """Reload the style library.""" 

269 library.clear() 

270 library.update(update_user_library(_base_library)) 

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

272 

273 

274reload_library()