Coverage for /usr/lib/python3/dist-packages/matplotlib/rcsetup.py: 69%
414 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"""
2The rcsetup module contains the validation code for customization using
3Matplotlib's rc settings.
5Each rc setting is assigned a function used to validate any attempted changes
6to that setting. The validation functions are defined in the rcsetup module,
7and are used to construct the rcParams global object which stores the settings
8and is referenced throughout Matplotlib.
10The default values of the rc settings are set in the default matplotlibrc file.
11Any additions or deletions to the parameter set listed here should also be
12propagated to the :file:`matplotlibrc.template` in Matplotlib's root source
13directory.
14"""
16import ast
17from functools import lru_cache, reduce
18from numbers import Number
19import operator
20import os
21import re
23import numpy as np
25from matplotlib import _api, cbook
26from matplotlib.cbook import ls_mapper
27from matplotlib.colors import Colormap, is_color_like
28from matplotlib._fontconfig_pattern import parse_fontconfig_pattern
29from matplotlib._enums import JoinStyle, CapStyle
31# Don't let the original cycler collide with our validating cycler
32from cycler import Cycler, cycler as ccycler
35# The capitalized forms are needed for ipython at present; this may
36# change for later versions.
37interactive_bk = [
38 'GTK3Agg', 'GTK3Cairo', 'GTK4Agg', 'GTK4Cairo',
39 'MacOSX',
40 'nbAgg',
41 'QtAgg', 'QtCairo', 'Qt5Agg', 'Qt5Cairo',
42 'TkAgg', 'TkCairo',
43 'WebAgg',
44 'WX', 'WXAgg', 'WXCairo',
45]
46non_interactive_bk = ['agg', 'cairo',
47 'pdf', 'pgf', 'ps', 'svg', 'template']
48all_backends = interactive_bk + non_interactive_bk
51class ValidateInStrings:
52 def __init__(self, key, valid, ignorecase=False, *,
53 _deprecated_since=None):
54 """*valid* is a list of legal strings."""
55 self.key = key
56 self.ignorecase = ignorecase
57 self._deprecated_since = _deprecated_since
59 def func(s):
60 if ignorecase:
61 return s.lower()
62 else:
63 return s
64 self.valid = {func(k): k for k in valid}
66 def __call__(self, s):
67 if self._deprecated_since:
68 name, = (k for k, v in globals().items() if v is self)
69 _api.warn_deprecated(
70 self._deprecated_since, name=name, obj_type="function")
71 if self.ignorecase and isinstance(s, str):
72 s = s.lower()
73 if s in self.valid:
74 return self.valid[s]
75 msg = (f"{s!r} is not a valid value for {self.key}; supported values "
76 f"are {[*self.valid.values()]}")
77 if (isinstance(s, str)
78 and (s.startswith('"') and s.endswith('"')
79 or s.startswith("'") and s.endswith("'"))
80 and s[1:-1] in self.valid):
81 msg += "; remove quotes surrounding your string"
82 raise ValueError(msg)
85@lru_cache()
86def _listify_validator(scalar_validator, allow_stringlist=False, *,
87 n=None, doc=None):
88 def f(s):
89 if isinstance(s, str):
90 try:
91 val = [scalar_validator(v.strip()) for v in s.split(',')
92 if v.strip()]
93 except Exception:
94 if allow_stringlist:
95 # Sometimes, a list of colors might be a single string
96 # of single-letter colornames. So give that a shot.
97 val = [scalar_validator(v.strip()) for v in s if v.strip()]
98 else:
99 raise
100 # Allow any ordered sequence type -- generators, np.ndarray, pd.Series
101 # -- but not sets, whose iteration order is non-deterministic.
102 elif np.iterable(s) and not isinstance(s, (set, frozenset)):
103 # The condition on this list comprehension will preserve the
104 # behavior of filtering out any empty strings (behavior was
105 # from the original validate_stringlist()), while allowing
106 # any non-string/text scalar values such as numbers and arrays.
107 val = [scalar_validator(v) for v in s
108 if not isinstance(v, str) or v]
109 else:
110 raise ValueError(
111 f"Expected str or other non-set iterable, but got {s}")
112 if n is not None and len(val) != n:
113 raise ValueError(
114 f"Expected {n} values, but there are {len(val)} values in {s}")
115 return val
117 try:
118 f.__name__ = "{}list".format(scalar_validator.__name__)
119 except AttributeError: # class instance.
120 f.__name__ = "{}List".format(type(scalar_validator).__name__)
121 f.__qualname__ = f.__qualname__.rsplit(".", 1)[0] + "." + f.__name__
122 f.__doc__ = doc if doc is not None else scalar_validator.__doc__
123 return f
126def validate_any(s):
127 return s
128validate_anylist = _listify_validator(validate_any)
131def _validate_date(s):
132 try:
133 np.datetime64(s)
134 return s
135 except ValueError:
136 raise ValueError(
137 f'{s!r} should be a string that can be parsed by numpy.datetime64')
140def validate_bool(b):
141 """Convert b to ``bool`` or raise."""
142 if isinstance(b, str):
143 b = b.lower()
144 if b in ('t', 'y', 'yes', 'on', 'true', '1', 1, True):
145 return True
146 elif b in ('f', 'n', 'no', 'off', 'false', '0', 0, False):
147 return False
148 else:
149 raise ValueError(f'Cannot convert {b!r} to bool')
152def validate_axisbelow(s):
153 try:
154 return validate_bool(s)
155 except ValueError:
156 if isinstance(s, str):
157 if s == 'line':
158 return 'line'
159 raise ValueError(f'{s!r} cannot be interpreted as'
160 ' True, False, or "line"')
163def validate_dpi(s):
164 """Confirm s is string 'figure' or convert s to float or raise."""
165 if s == 'figure':
166 return s
167 try:
168 return float(s)
169 except ValueError as e:
170 raise ValueError(f'{s!r} is not string "figure" and '
171 f'could not convert {s!r} to float') from e
174def _make_type_validator(cls, *, allow_none=False):
175 """
176 Return a validator that converts inputs to *cls* or raises (and possibly
177 allows ``None`` as well).
178 """
180 def validator(s):
181 if (allow_none and
182 (s is None or isinstance(s, str) and s.lower() == "none")):
183 return None
184 if cls is str and not isinstance(s, str):
185 _api.warn_deprecated(
186 "3.5", message="Support for setting an rcParam that expects a "
187 "str value to a non-str value is deprecated since %(since)s "
188 "and support will be removed %(removal)s.")
189 try:
190 return cls(s)
191 except (TypeError, ValueError) as e:
192 raise ValueError(
193 f'Could not convert {s!r} to {cls.__name__}') from e
195 validator.__name__ = f"validate_{cls.__name__}"
196 if allow_none:
197 validator.__name__ += "_or_None"
198 validator.__qualname__ = (
199 validator.__qualname__.rsplit(".", 1)[0] + "." + validator.__name__)
200 return validator
203validate_string = _make_type_validator(str)
204validate_string_or_None = _make_type_validator(str, allow_none=True)
205validate_stringlist = _listify_validator(
206 validate_string, doc='return a list of strings')
207validate_int = _make_type_validator(int)
208validate_int_or_None = _make_type_validator(int, allow_none=True)
209validate_float = _make_type_validator(float)
210validate_float_or_None = _make_type_validator(float, allow_none=True)
211validate_floatlist = _listify_validator(
212 validate_float, doc='return a list of floats')
215def _validate_pathlike(s):
216 if isinstance(s, (str, os.PathLike)):
217 # Store value as str because savefig.directory needs to distinguish
218 # between "" (cwd) and "." (cwd, but gets updated by user selections).
219 return os.fsdecode(s)
220 else:
221 return validate_string(s) # Emit deprecation warning.
224def validate_fonttype(s):
225 """
226 Confirm that this is a Postscript or PDF font type that we know how to
227 convert to.
228 """
229 fonttypes = {'type3': 3,
230 'truetype': 42}
231 try:
232 fonttype = validate_int(s)
233 except ValueError:
234 try:
235 return fonttypes[s.lower()]
236 except KeyError as e:
237 raise ValueError('Supported Postscript/PDF font types are %s'
238 % list(fonttypes)) from e
239 else:
240 if fonttype not in fonttypes.values():
241 raise ValueError(
242 'Supported Postscript/PDF font types are %s' %
243 list(fonttypes.values()))
244 return fonttype
247_validate_standard_backends = ValidateInStrings(
248 'backend', all_backends, ignorecase=True)
249_auto_backend_sentinel = object()
252def validate_backend(s):
253 backend = (
254 s if s is _auto_backend_sentinel or s.startswith("module://")
255 else _validate_standard_backends(s))
256 return backend
259def _validate_toolbar(s):
260 s = ValidateInStrings(
261 'toolbar', ['None', 'toolbar2', 'toolmanager'], ignorecase=True)(s)
262 if s == 'toolmanager':
263 _api.warn_external(
264 "Treat the new Tool classes introduced in v1.5 as experimental "
265 "for now; the API and rcParam may change in future versions.")
266 return s
269def validate_color_or_inherit(s):
270 """Return a valid color arg."""
271 if cbook._str_equal(s, 'inherit'):
272 return s
273 return validate_color(s)
276def validate_color_or_auto(s):
277 if cbook._str_equal(s, 'auto'):
278 return s
279 return validate_color(s)
282def validate_color_for_prop_cycle(s):
283 # N-th color cycle syntax can't go into the color cycle.
284 if isinstance(s, str) and re.match("^C[0-9]$", s):
285 raise ValueError(f"Cannot put cycle reference ({s!r}) in prop_cycler")
286 return validate_color(s)
289def _validate_color_or_linecolor(s):
290 if cbook._str_equal(s, 'linecolor'):
291 return s
292 elif cbook._str_equal(s, 'mfc') or cbook._str_equal(s, 'markerfacecolor'):
293 return 'markerfacecolor'
294 elif cbook._str_equal(s, 'mec') or cbook._str_equal(s, 'markeredgecolor'):
295 return 'markeredgecolor'
296 elif s is None:
297 return None
298 elif isinstance(s, str) and len(s) == 6 or len(s) == 8:
299 stmp = '#' + s
300 if is_color_like(stmp):
301 return stmp
302 if s.lower() == 'none':
303 return None
304 elif is_color_like(s):
305 return s
307 raise ValueError(f'{s!r} does not look like a color arg')
310def validate_color(s):
311 """Return a valid color arg."""
312 if isinstance(s, str):
313 if s.lower() == 'none':
314 return 'none'
315 if len(s) == 6 or len(s) == 8:
316 stmp = '#' + s
317 if is_color_like(stmp):
318 return stmp
320 if is_color_like(s):
321 return s
323 # If it is still valid, it must be a tuple (as a string from matplotlibrc).
324 try:
325 color = ast.literal_eval(s)
326 except (SyntaxError, ValueError):
327 pass
328 else:
329 if is_color_like(color):
330 return color
332 raise ValueError(f'{s!r} does not look like a color arg')
335validate_colorlist = _listify_validator(
336 validate_color, allow_stringlist=True, doc='return a list of colorspecs')
339def _validate_cmap(s):
340 _api.check_isinstance((str, Colormap), cmap=s)
341 return s
344def validate_aspect(s):
345 if s in ('auto', 'equal'):
346 return s
347 try:
348 return float(s)
349 except ValueError as e:
350 raise ValueError('not a valid aspect specification') from e
353def validate_fontsize_None(s):
354 if s is None or s == 'None':
355 return None
356 else:
357 return validate_fontsize(s)
360def validate_fontsize(s):
361 fontsizes = ['xx-small', 'x-small', 'small', 'medium', 'large',
362 'x-large', 'xx-large', 'smaller', 'larger']
363 if isinstance(s, str):
364 s = s.lower()
365 if s in fontsizes:
366 return s
367 try:
368 return float(s)
369 except ValueError as e:
370 raise ValueError("%s is not a valid font size. Valid font sizes "
371 "are %s." % (s, ", ".join(fontsizes))) from e
374validate_fontsizelist = _listify_validator(validate_fontsize)
377def validate_fontweight(s):
378 weights = [
379 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', 'roman',
380 'semibold', 'demibold', 'demi', 'bold', 'heavy', 'extra bold', 'black']
381 # Note: Historically, weights have been case-sensitive in Matplotlib
382 if s in weights:
383 return s
384 try:
385 return int(s)
386 except (ValueError, TypeError) as e:
387 raise ValueError(f'{s} is not a valid font weight.') from e
390def validate_fontstretch(s):
391 stretchvalues = [
392 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed',
393 'normal', 'semi-expanded', 'expanded', 'extra-expanded',
394 'ultra-expanded']
395 # Note: Historically, stretchvalues have been case-sensitive in Matplotlib
396 if s in stretchvalues:
397 return s
398 try:
399 return int(s)
400 except (ValueError, TypeError) as e:
401 raise ValueError(f'{s} is not a valid font stretch.') from e
404def validate_font_properties(s):
405 parse_fontconfig_pattern(s)
406 return s
409def _validate_mathtext_fallback(s):
410 _fallback_fonts = ['cm', 'stix', 'stixsans']
411 if isinstance(s, str):
412 s = s.lower()
413 if s is None or s == 'none':
414 return None
415 elif s.lower() in _fallback_fonts:
416 return s
417 else:
418 raise ValueError(
419 f"{s} is not a valid fallback font name. Valid fallback font "
420 f"names are {','.join(_fallback_fonts)}. Passing 'None' will turn "
421 "fallback off.")
424def validate_whiskers(s):
425 try:
426 return _listify_validator(validate_float, n=2)(s)
427 except (TypeError, ValueError):
428 try:
429 return float(s)
430 except ValueError as e:
431 raise ValueError("Not a valid whisker value [float, "
432 "(float, float)]") from e
435def validate_ps_distiller(s):
436 if isinstance(s, str):
437 s = s.lower()
438 if s in ('none', None, 'false', False):
439 return None
440 else:
441 return ValidateInStrings('ps.usedistiller', ['ghostscript', 'xpdf'])(s)
444# A validator dedicated to the named line styles, based on the items in
445# ls_mapper, and a list of possible strings read from Line2D.set_linestyle
446_validate_named_linestyle = ValidateInStrings(
447 'linestyle',
448 [*ls_mapper.keys(), *ls_mapper.values(), 'None', 'none', ' ', ''],
449 ignorecase=True)
452def _validate_linestyle(ls):
453 """
454 A validator for all possible line styles, the named ones *and*
455 the on-off ink sequences.
456 """
457 if isinstance(ls, str):
458 try: # Look first for a valid named line style, like '--' or 'solid'.
459 return _validate_named_linestyle(ls)
460 except ValueError:
461 pass
462 try:
463 ls = ast.literal_eval(ls) # Parsing matplotlibrc.
464 except (SyntaxError, ValueError):
465 pass # Will error with the ValueError at the end.
467 def _is_iterable_not_string_like(x):
468 # Explicitly exclude bytes/bytearrays so that they are not
469 # nonsensically interpreted as sequences of numbers (codepoints).
470 return np.iterable(x) and not isinstance(x, (str, bytes, bytearray))
472 if _is_iterable_not_string_like(ls):
473 if len(ls) == 2 and _is_iterable_not_string_like(ls[1]):
474 # (offset, (on, off, on, off, ...))
475 offset, onoff = ls
476 else:
477 # For backcompat: (on, off, on, off, ...); the offset is implicit.
478 offset = 0
479 onoff = ls
481 if (isinstance(offset, Number)
482 and len(onoff) % 2 == 0
483 and all(isinstance(elem, Number) for elem in onoff)):
484 return (offset, onoff)
486 raise ValueError(f"linestyle {ls!r} is not a valid on-off ink sequence.")
489validate_fillstyle = ValidateInStrings(
490 'markers.fillstyle', ['full', 'left', 'right', 'bottom', 'top', 'none'])
493validate_fillstylelist = _listify_validator(validate_fillstyle)
496def validate_markevery(s):
497 """
498 Validate the markevery property of a Line2D object.
500 Parameters
501 ----------
502 s : None, int, (int, int), slice, float, (float, float), or list[int]
504 Returns
505 -------
506 None, int, (int, int), slice, float, (float, float), or list[int]
507 """
508 # Validate s against type slice float int and None
509 if isinstance(s, (slice, float, int, type(None))):
510 return s
511 # Validate s against type tuple
512 if isinstance(s, tuple):
513 if (len(s) == 2
514 and (all(isinstance(e, int) for e in s)
515 or all(isinstance(e, float) for e in s))):
516 return s
517 else:
518 raise TypeError(
519 "'markevery' tuple must be pair of ints or of floats")
520 # Validate s against type list
521 if isinstance(s, list):
522 if all(isinstance(e, int) for e in s):
523 return s
524 else:
525 raise TypeError(
526 "'markevery' list must have all elements of type int")
527 raise TypeError("'markevery' is of an invalid type")
530validate_markeverylist = _listify_validator(validate_markevery)
533def validate_bbox(s):
534 if isinstance(s, str):
535 s = s.lower()
536 if s == 'tight':
537 return s
538 if s == 'standard':
539 return None
540 raise ValueError("bbox should be 'tight' or 'standard'")
541 elif s is not None:
542 # Backwards compatibility. None is equivalent to 'standard'.
543 raise ValueError("bbox should be 'tight' or 'standard'")
544 return s
547def validate_sketch(s):
548 if isinstance(s, str):
549 s = s.lower()
550 if s == 'none' or s is None:
551 return None
552 try:
553 return tuple(_listify_validator(validate_float, n=3)(s))
554 except ValueError:
555 raise ValueError("Expected a (scale, length, randomness) triplet")
558def _validate_greaterequal0_lessthan1(s):
559 s = validate_float(s)
560 if 0 <= s < 1:
561 return s
562 else:
563 raise RuntimeError(f'Value must be >=0 and <1; got {s}')
566def _validate_greaterequal0_lessequal1(s):
567 s = validate_float(s)
568 if 0 <= s <= 1:
569 return s
570 else:
571 raise RuntimeError(f'Value must be >=0 and <=1; got {s}')
574_range_validators = { # Slightly nicer (internal) API.
575 "0 <= x < 1": _validate_greaterequal0_lessthan1,
576 "0 <= x <= 1": _validate_greaterequal0_lessequal1,
577}
580def validate_hatch(s):
581 r"""
582 Validate a hatch pattern.
583 A hatch pattern string can have any sequence of the following
584 characters: ``\ / | - + * . x o O``.
585 """
586 if not isinstance(s, str):
587 raise ValueError("Hatch pattern must be a string")
588 _api.check_isinstance(str, hatch_pattern=s)
589 unknown = set(s) - {'\\', '/', '|', '-', '+', '*', '.', 'x', 'o', 'O'}
590 if unknown:
591 raise ValueError("Unknown hatch symbol(s): %s" % list(unknown))
592 return s
595validate_hatchlist = _listify_validator(validate_hatch)
596validate_dashlist = _listify_validator(validate_floatlist)
599_prop_validators = {
600 'color': _listify_validator(validate_color_for_prop_cycle,
601 allow_stringlist=True),
602 'linewidth': validate_floatlist,
603 'linestyle': _listify_validator(_validate_linestyle),
604 'facecolor': validate_colorlist,
605 'edgecolor': validate_colorlist,
606 'joinstyle': _listify_validator(JoinStyle),
607 'capstyle': _listify_validator(CapStyle),
608 'fillstyle': validate_fillstylelist,
609 'markerfacecolor': validate_colorlist,
610 'markersize': validate_floatlist,
611 'markeredgewidth': validate_floatlist,
612 'markeredgecolor': validate_colorlist,
613 'markevery': validate_markeverylist,
614 'alpha': validate_floatlist,
615 'marker': validate_stringlist,
616 'hatch': validate_hatchlist,
617 'dashes': validate_dashlist,
618 }
619_prop_aliases = {
620 'c': 'color',
621 'lw': 'linewidth',
622 'ls': 'linestyle',
623 'fc': 'facecolor',
624 'ec': 'edgecolor',
625 'mfc': 'markerfacecolor',
626 'mec': 'markeredgecolor',
627 'mew': 'markeredgewidth',
628 'ms': 'markersize',
629 }
632def cycler(*args, **kwargs):
633 """
634 Create a `~cycler.Cycler` object much like :func:`cycler.cycler`,
635 but includes input validation.
637 Call signatures::
639 cycler(cycler)
640 cycler(label=values[, label2=values2[, ...]])
641 cycler(label, values)
643 Form 1 copies a given `~cycler.Cycler` object.
645 Form 2 creates a `~cycler.Cycler` which cycles over one or more
646 properties simultaneously. If multiple properties are given, their
647 value lists must have the same length.
649 Form 3 creates a `~cycler.Cycler` for a single property. This form
650 exists for compatibility with the original cycler. Its use is
651 discouraged in favor of the kwarg form, i.e. ``cycler(label=values)``.
653 Parameters
654 ----------
655 cycler : Cycler
656 Copy constructor for Cycler.
658 label : str
659 The property key. Must be a valid `.Artist` property.
660 For example, 'color' or 'linestyle'. Aliases are allowed,
661 such as 'c' for 'color' and 'lw' for 'linewidth'.
663 values : iterable
664 Finite-length iterable of the property values. These values
665 are validated and will raise a ValueError if invalid.
667 Returns
668 -------
669 Cycler
670 A new :class:`~cycler.Cycler` for the given properties.
672 Examples
673 --------
674 Creating a cycler for a single property:
676 >>> c = cycler(color=['red', 'green', 'blue'])
678 Creating a cycler for simultaneously cycling over multiple properties
679 (e.g. red circle, green plus, blue cross):
681 >>> c = cycler(color=['red', 'green', 'blue'],
682 ... marker=['o', '+', 'x'])
684 """
685 if args and kwargs:
686 raise TypeError("cycler() can only accept positional OR keyword "
687 "arguments -- not both.")
688 elif not args and not kwargs:
689 raise TypeError("cycler() must have positional OR keyword arguments")
691 if len(args) == 1:
692 if not isinstance(args[0], Cycler):
693 raise TypeError("If only one positional argument given, it must "
694 "be a Cycler instance.")
695 return validate_cycler(args[0])
696 elif len(args) == 2:
697 pairs = [(args[0], args[1])]
698 elif len(args) > 2:
699 raise TypeError("No more than 2 positional arguments allowed")
700 else:
701 pairs = kwargs.items()
703 validated = []
704 for prop, vals in pairs:
705 norm_prop = _prop_aliases.get(prop, prop)
706 validator = _prop_validators.get(norm_prop, None)
707 if validator is None:
708 raise TypeError("Unknown artist property: %s" % prop)
709 vals = validator(vals)
710 # We will normalize the property names as well to reduce
711 # the amount of alias handling code elsewhere.
712 validated.append((norm_prop, vals))
714 return reduce(operator.add, (ccycler(k, v) for k, v in validated))
717class _DunderChecker(ast.NodeVisitor):
718 def visit_Attribute(self, node):
719 if node.attr.startswith("__") and node.attr.endswith("__"):
720 raise ValueError("cycler strings with dunders are forbidden")
721 self.generic_visit(node)
724def validate_cycler(s):
725 """Return a Cycler object from a string repr or the object itself."""
726 if isinstance(s, str):
727 # TODO: We might want to rethink this...
728 # While I think I have it quite locked down, it is execution of
729 # arbitrary code without sanitation.
730 # Combine this with the possibility that rcparams might come from the
731 # internet (future plans), this could be downright dangerous.
732 # I locked it down by only having the 'cycler()' function available.
733 # UPDATE: Partly plugging a security hole.
734 # I really should have read this:
735 # https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
736 # We should replace this eval with a combo of PyParsing and
737 # ast.literal_eval()
738 try:
739 _DunderChecker().visit(ast.parse(s))
740 s = eval(s, {'cycler': cycler, '__builtins__': {}})
741 except BaseException as e:
742 raise ValueError(f"{s!r} is not a valid cycler construction: {e}"
743 ) from e
744 # Should make sure what comes from the above eval()
745 # is a Cycler object.
746 if isinstance(s, Cycler):
747 cycler_inst = s
748 else:
749 raise ValueError(f"Object is not a string or Cycler instance: {s!r}")
751 unknowns = cycler_inst.keys - (set(_prop_validators) | set(_prop_aliases))
752 if unknowns:
753 raise ValueError("Unknown artist properties: %s" % unknowns)
755 # Not a full validation, but it'll at least normalize property names
756 # A fuller validation would require v0.10 of cycler.
757 checker = set()
758 for prop in cycler_inst.keys:
759 norm_prop = _prop_aliases.get(prop, prop)
760 if norm_prop != prop and norm_prop in cycler_inst.keys:
761 raise ValueError(f"Cannot specify both {norm_prop!r} and alias "
762 f"{prop!r} in the same prop_cycle")
763 if norm_prop in checker:
764 raise ValueError(f"Another property was already aliased to "
765 f"{norm_prop!r}. Collision normalizing {prop!r}.")
766 checker.update([norm_prop])
768 # This is just an extra-careful check, just in case there is some
769 # edge-case I haven't thought of.
770 assert len(checker) == len(cycler_inst.keys)
772 # Now, it should be safe to mutate this cycler
773 for prop in cycler_inst.keys:
774 norm_prop = _prop_aliases.get(prop, prop)
775 cycler_inst.change_key(prop, norm_prop)
777 for key, vals in cycler_inst.by_key().items():
778 _prop_validators[key](vals)
780 return cycler_inst
783def validate_hist_bins(s):
784 valid_strs = ["auto", "sturges", "fd", "doane", "scott", "rice", "sqrt"]
785 if isinstance(s, str) and s in valid_strs:
786 return s
787 try:
788 return int(s)
789 except (TypeError, ValueError):
790 pass
791 try:
792 return validate_floatlist(s)
793 except ValueError:
794 pass
795 raise ValueError("'hist.bins' must be one of {}, an int or"
796 " a sequence of floats".format(valid_strs))
799class _ignorecase(list):
800 """A marker class indicating that a list-of-str is case-insensitive."""
803def _convert_validator_spec(key, conv):
804 if isinstance(conv, list):
805 ignorecase = isinstance(conv, _ignorecase)
806 return ValidateInStrings(key, conv, ignorecase=ignorecase)
807 else:
808 return conv
811# Mapping of rcParams to validators.
812# Converters given as lists or _ignorecase are converted to ValidateInStrings
813# immediately below.
814# The rcParams defaults are defined in matplotlibrc.template, which gets copied
815# to matplotlib/mpl-data/matplotlibrc by the setup script.
816_validators = {
817 "backend": validate_backend,
818 "backend_fallback": validate_bool,
819 "toolbar": _validate_toolbar,
820 "interactive": validate_bool,
821 "timezone": validate_string,
823 "webagg.port": validate_int,
824 "webagg.address": validate_string,
825 "webagg.open_in_browser": validate_bool,
826 "webagg.port_retries": validate_int,
828 # line props
829 "lines.linewidth": validate_float, # line width in points
830 "lines.linestyle": _validate_linestyle, # solid line
831 "lines.color": validate_color, # first color in color cycle
832 "lines.marker": validate_string, # marker name
833 "lines.markerfacecolor": validate_color_or_auto, # default color
834 "lines.markeredgecolor": validate_color_or_auto, # default color
835 "lines.markeredgewidth": validate_float,
836 "lines.markersize": validate_float, # markersize, in points
837 "lines.antialiased": validate_bool, # antialiased (no jaggies)
838 "lines.dash_joinstyle": JoinStyle,
839 "lines.solid_joinstyle": JoinStyle,
840 "lines.dash_capstyle": CapStyle,
841 "lines.solid_capstyle": CapStyle,
842 "lines.dashed_pattern": validate_floatlist,
843 "lines.dashdot_pattern": validate_floatlist,
844 "lines.dotted_pattern": validate_floatlist,
845 "lines.scale_dashes": validate_bool,
847 # marker props
848 "markers.fillstyle": validate_fillstyle,
850 ## pcolor(mesh) props:
851 "pcolor.shading": ["auto", "flat", "nearest", "gouraud"],
852 "pcolormesh.snap": validate_bool,
854 ## patch props
855 "patch.linewidth": validate_float, # line width in points
856 "patch.edgecolor": validate_color,
857 "patch.force_edgecolor": validate_bool,
858 "patch.facecolor": validate_color, # first color in cycle
859 "patch.antialiased": validate_bool, # antialiased (no jaggies)
861 ## hatch props
862 "hatch.color": validate_color,
863 "hatch.linewidth": validate_float,
865 ## Histogram properties
866 "hist.bins": validate_hist_bins,
868 ## Boxplot properties
869 "boxplot.notch": validate_bool,
870 "boxplot.vertical": validate_bool,
871 "boxplot.whiskers": validate_whiskers,
872 "boxplot.bootstrap": validate_int_or_None,
873 "boxplot.patchartist": validate_bool,
874 "boxplot.showmeans": validate_bool,
875 "boxplot.showcaps": validate_bool,
876 "boxplot.showbox": validate_bool,
877 "boxplot.showfliers": validate_bool,
878 "boxplot.meanline": validate_bool,
880 "boxplot.flierprops.color": validate_color,
881 "boxplot.flierprops.marker": validate_string,
882 "boxplot.flierprops.markerfacecolor": validate_color_or_auto,
883 "boxplot.flierprops.markeredgecolor": validate_color,
884 "boxplot.flierprops.markeredgewidth": validate_float,
885 "boxplot.flierprops.markersize": validate_float,
886 "boxplot.flierprops.linestyle": _validate_linestyle,
887 "boxplot.flierprops.linewidth": validate_float,
889 "boxplot.boxprops.color": validate_color,
890 "boxplot.boxprops.linewidth": validate_float,
891 "boxplot.boxprops.linestyle": _validate_linestyle,
893 "boxplot.whiskerprops.color": validate_color,
894 "boxplot.whiskerprops.linewidth": validate_float,
895 "boxplot.whiskerprops.linestyle": _validate_linestyle,
897 "boxplot.capprops.color": validate_color,
898 "boxplot.capprops.linewidth": validate_float,
899 "boxplot.capprops.linestyle": _validate_linestyle,
901 "boxplot.medianprops.color": validate_color,
902 "boxplot.medianprops.linewidth": validate_float,
903 "boxplot.medianprops.linestyle": _validate_linestyle,
905 "boxplot.meanprops.color": validate_color,
906 "boxplot.meanprops.marker": validate_string,
907 "boxplot.meanprops.markerfacecolor": validate_color,
908 "boxplot.meanprops.markeredgecolor": validate_color,
909 "boxplot.meanprops.markersize": validate_float,
910 "boxplot.meanprops.linestyle": _validate_linestyle,
911 "boxplot.meanprops.linewidth": validate_float,
913 ## font props
914 "font.family": validate_stringlist, # used by text object
915 "font.style": validate_string,
916 "font.variant": validate_string,
917 "font.stretch": validate_fontstretch,
918 "font.weight": validate_fontweight,
919 "font.size": validate_float, # Base font size in points
920 "font.serif": validate_stringlist,
921 "font.sans-serif": validate_stringlist,
922 "font.cursive": validate_stringlist,
923 "font.fantasy": validate_stringlist,
924 "font.monospace": validate_stringlist,
926 # text props
927 "text.color": validate_color,
928 "text.usetex": validate_bool,
929 "text.latex.preamble": validate_string,
930 "text.hinting": ["default", "no_autohint", "force_autohint",
931 "no_hinting", "auto", "native", "either", "none"],
932 "text.hinting_factor": validate_int,
933 "text.kerning_factor": validate_int,
934 "text.antialiased": validate_bool,
935 "text.parse_math": validate_bool,
937 "mathtext.cal": validate_font_properties,
938 "mathtext.rm": validate_font_properties,
939 "mathtext.tt": validate_font_properties,
940 "mathtext.it": validate_font_properties,
941 "mathtext.bf": validate_font_properties,
942 "mathtext.sf": validate_font_properties,
943 "mathtext.fontset": ["dejavusans", "dejavuserif", "cm", "stix",
944 "stixsans", "custom"],
945 "mathtext.default": ["rm", "cal", "it", "tt", "sf", "bf", "default",
946 "bb", "frak", "scr", "regular"],
947 "mathtext.fallback": _validate_mathtext_fallback,
949 "image.aspect": validate_aspect, # equal, auto, a number
950 "image.interpolation": validate_string,
951 "image.cmap": _validate_cmap, # gray, jet, etc.
952 "image.lut": validate_int, # lookup table
953 "image.origin": ["upper", "lower"],
954 "image.resample": validate_bool,
955 # Specify whether vector graphics backends will combine all images on a
956 # set of axes into a single composite image
957 "image.composite_image": validate_bool,
959 # contour props
960 "contour.negative_linestyle": _validate_linestyle,
961 "contour.corner_mask": validate_bool,
962 "contour.linewidth": validate_float_or_None,
963 "contour.algorithm": ["mpl2005", "mpl2014", "serial", "threaded"],
965 # errorbar props
966 "errorbar.capsize": validate_float,
968 # axis props
969 # alignment of x/y axis title
970 "xaxis.labellocation": ["left", "center", "right"],
971 "yaxis.labellocation": ["bottom", "center", "top"],
973 # axes props
974 "axes.axisbelow": validate_axisbelow,
975 "axes.facecolor": validate_color, # background color
976 "axes.edgecolor": validate_color, # edge color
977 "axes.linewidth": validate_float, # edge linewidth
979 "axes.spines.left": validate_bool, # Set visibility of axes spines,
980 "axes.spines.right": validate_bool, # i.e., the lines around the chart
981 "axes.spines.bottom": validate_bool, # denoting data boundary.
982 "axes.spines.top": validate_bool,
984 "axes.titlesize": validate_fontsize, # axes title fontsize
985 "axes.titlelocation": ["left", "center", "right"], # axes title alignment
986 "axes.titleweight": validate_fontweight, # axes title font weight
987 "axes.titlecolor": validate_color_or_auto, # axes title font color
988 # title location, axes units, None means auto
989 "axes.titley": validate_float_or_None,
990 # pad from axes top decoration to title in points
991 "axes.titlepad": validate_float,
992 "axes.grid": validate_bool, # display grid or not
993 "axes.grid.which": ["minor", "both", "major"], # which grids are drawn
994 "axes.grid.axis": ["x", "y", "both"], # grid type
995 "axes.labelsize": validate_fontsize, # fontsize of x & y labels
996 "axes.labelpad": validate_float, # space between label and axis
997 "axes.labelweight": validate_fontweight, # fontsize of x & y labels
998 "axes.labelcolor": validate_color, # color of axis label
999 # use scientific notation if log10 of the axis range is smaller than the
1000 # first or larger than the second
1001 "axes.formatter.limits": _listify_validator(validate_int, n=2),
1002 # use current locale to format ticks
1003 "axes.formatter.use_locale": validate_bool,
1004 "axes.formatter.use_mathtext": validate_bool,
1005 # minimum exponent to format in scientific notation
1006 "axes.formatter.min_exponent": validate_int,
1007 "axes.formatter.useoffset": validate_bool,
1008 "axes.formatter.offset_threshold": validate_int,
1009 "axes.unicode_minus": validate_bool,
1010 # This entry can be either a cycler object or a string repr of a
1011 # cycler-object, which gets eval()'ed to create the object.
1012 "axes.prop_cycle": validate_cycler,
1013 # If "data", axes limits are set close to the data.
1014 # If "round_numbers" axes limits are set to the nearest round numbers.
1015 "axes.autolimit_mode": ["data", "round_numbers"],
1016 "axes.xmargin": _range_validators["0 <= x <= 1"], # margin added to xaxis
1017 "axes.ymargin": _range_validators["0 <= x <= 1"], # margin added to yaxis
1018 'axes.zmargin': _range_validators["0 <= x <= 1"], # margin added to zaxis
1020 "polaraxes.grid": validate_bool, # display polar grid or not
1021 "axes3d.grid": validate_bool, # display 3d grid
1023 # scatter props
1024 "scatter.marker": validate_string,
1025 "scatter.edgecolors": validate_string,
1027 "date.epoch": _validate_date,
1028 "date.autoformatter.year": validate_string,
1029 "date.autoformatter.month": validate_string,
1030 "date.autoformatter.day": validate_string,
1031 "date.autoformatter.hour": validate_string,
1032 "date.autoformatter.minute": validate_string,
1033 "date.autoformatter.second": validate_string,
1034 "date.autoformatter.microsecond": validate_string,
1036 'date.converter': ['auto', 'concise'],
1037 # for auto date locator, choose interval_multiples
1038 'date.interval_multiples': validate_bool,
1040 # legend properties
1041 "legend.fancybox": validate_bool,
1042 "legend.loc": _ignorecase([
1043 "best",
1044 "upper right", "upper left", "lower left", "lower right", "right",
1045 "center left", "center right", "lower center", "upper center",
1046 "center"]),
1048 # the number of points in the legend line
1049 "legend.numpoints": validate_int,
1050 # the number of points in the legend line for scatter
1051 "legend.scatterpoints": validate_int,
1052 "legend.fontsize": validate_fontsize,
1053 "legend.title_fontsize": validate_fontsize_None,
1054 # color of the legend
1055 "legend.labelcolor": _validate_color_or_linecolor,
1056 # the relative size of legend markers vs. original
1057 "legend.markerscale": validate_float,
1058 "legend.shadow": validate_bool,
1059 # whether or not to draw a frame around legend
1060 "legend.frameon": validate_bool,
1061 # alpha value of the legend frame
1062 "legend.framealpha": validate_float_or_None,
1064 ## the following dimensions are in fraction of the font size
1065 "legend.borderpad": validate_float, # units are fontsize
1066 # the vertical space between the legend entries
1067 "legend.labelspacing": validate_float,
1068 # the length of the legend lines
1069 "legend.handlelength": validate_float,
1070 # the length of the legend lines
1071 "legend.handleheight": validate_float,
1072 # the space between the legend line and legend text
1073 "legend.handletextpad": validate_float,
1074 # the border between the axes and legend edge
1075 "legend.borderaxespad": validate_float,
1076 # the border between the axes and legend edge
1077 "legend.columnspacing": validate_float,
1078 "legend.facecolor": validate_color_or_inherit,
1079 "legend.edgecolor": validate_color_or_inherit,
1081 # tick properties
1082 "xtick.top": validate_bool, # draw ticks on top side
1083 "xtick.bottom": validate_bool, # draw ticks on bottom side
1084 "xtick.labeltop": validate_bool, # draw label on top
1085 "xtick.labelbottom": validate_bool, # draw label on bottom
1086 "xtick.major.size": validate_float, # major xtick size in points
1087 "xtick.minor.size": validate_float, # minor xtick size in points
1088 "xtick.major.width": validate_float, # major xtick width in points
1089 "xtick.minor.width": validate_float, # minor xtick width in points
1090 "xtick.major.pad": validate_float, # distance to label in points
1091 "xtick.minor.pad": validate_float, # distance to label in points
1092 "xtick.color": validate_color, # color of xticks
1093 "xtick.labelcolor": validate_color_or_inherit, # color of xtick labels
1094 "xtick.minor.visible": validate_bool, # visibility of minor xticks
1095 "xtick.minor.top": validate_bool, # draw top minor xticks
1096 "xtick.minor.bottom": validate_bool, # draw bottom minor xticks
1097 "xtick.major.top": validate_bool, # draw top major xticks
1098 "xtick.major.bottom": validate_bool, # draw bottom major xticks
1099 "xtick.labelsize": validate_fontsize, # fontsize of xtick labels
1100 "xtick.direction": ["out", "in", "inout"], # direction of xticks
1101 "xtick.alignment": ["center", "right", "left"],
1103 "ytick.left": validate_bool, # draw ticks on left side
1104 "ytick.right": validate_bool, # draw ticks on right side
1105 "ytick.labelleft": validate_bool, # draw tick labels on left side
1106 "ytick.labelright": validate_bool, # draw tick labels on right side
1107 "ytick.major.size": validate_float, # major ytick size in points
1108 "ytick.minor.size": validate_float, # minor ytick size in points
1109 "ytick.major.width": validate_float, # major ytick width in points
1110 "ytick.minor.width": validate_float, # minor ytick width in points
1111 "ytick.major.pad": validate_float, # distance to label in points
1112 "ytick.minor.pad": validate_float, # distance to label in points
1113 "ytick.color": validate_color, # color of yticks
1114 "ytick.labelcolor": validate_color_or_inherit, # color of ytick labels
1115 "ytick.minor.visible": validate_bool, # visibility of minor yticks
1116 "ytick.minor.left": validate_bool, # draw left minor yticks
1117 "ytick.minor.right": validate_bool, # draw right minor yticks
1118 "ytick.major.left": validate_bool, # draw left major yticks
1119 "ytick.major.right": validate_bool, # draw right major yticks
1120 "ytick.labelsize": validate_fontsize, # fontsize of ytick labels
1121 "ytick.direction": ["out", "in", "inout"], # direction of yticks
1122 "ytick.alignment": [
1123 "center", "top", "bottom", "baseline", "center_baseline"],
1125 "grid.color": validate_color, # grid color
1126 "grid.linestyle": _validate_linestyle, # solid
1127 "grid.linewidth": validate_float, # in points
1128 "grid.alpha": validate_float,
1130 ## figure props
1131 # figure title
1132 "figure.titlesize": validate_fontsize,
1133 "figure.titleweight": validate_fontweight,
1135 # figure labels
1136 "figure.labelsize": validate_fontsize,
1137 "figure.labelweight": validate_fontweight,
1139 # figure size in inches: width by height
1140 "figure.figsize": _listify_validator(validate_float, n=2),
1141 "figure.dpi": validate_float,
1142 "figure.facecolor": validate_color,
1143 "figure.edgecolor": validate_color,
1144 "figure.frameon": validate_bool,
1145 "figure.autolayout": validate_bool,
1146 "figure.max_open_warning": validate_int,
1147 "figure.raise_window": validate_bool,
1149 "figure.subplot.left": _range_validators["0 <= x <= 1"],
1150 "figure.subplot.right": _range_validators["0 <= x <= 1"],
1151 "figure.subplot.bottom": _range_validators["0 <= x <= 1"],
1152 "figure.subplot.top": _range_validators["0 <= x <= 1"],
1153 "figure.subplot.wspace": _range_validators["0 <= x < 1"],
1154 "figure.subplot.hspace": _range_validators["0 <= x < 1"],
1156 "figure.constrained_layout.use": validate_bool, # run constrained_layout?
1157 # wspace and hspace are fraction of adjacent subplots to use for space.
1158 # Much smaller than above because we don't need room for the text.
1159 "figure.constrained_layout.hspace": _range_validators["0 <= x < 1"],
1160 "figure.constrained_layout.wspace": _range_validators["0 <= x < 1"],
1161 # buffer around the axes, in inches.
1162 'figure.constrained_layout.h_pad': validate_float,
1163 'figure.constrained_layout.w_pad': validate_float,
1165 ## Saving figure's properties
1166 'savefig.dpi': validate_dpi,
1167 'savefig.facecolor': validate_color_or_auto,
1168 'savefig.edgecolor': validate_color_or_auto,
1169 'savefig.orientation': ['landscape', 'portrait'],
1170 "savefig.format": validate_string,
1171 "savefig.bbox": validate_bbox, # "tight", or "standard" (= None)
1172 "savefig.pad_inches": validate_float,
1173 # default directory in savefig dialog box
1174 "savefig.directory": _validate_pathlike,
1175 "savefig.transparent": validate_bool,
1177 "tk.window_focus": validate_bool, # Maintain shell focus for TkAgg
1179 # Set the papersize/type
1180 "ps.papersize": _ignorecase(["auto", "letter", "legal", "ledger",
1181 *[f"{ab}{i}"
1182 for ab in "ab" for i in range(11)]]),
1183 "ps.useafm": validate_bool,
1184 # use ghostscript or xpdf to distill ps output
1185 "ps.usedistiller": validate_ps_distiller,
1186 "ps.distiller.res": validate_int, # dpi
1187 "ps.fonttype": validate_fonttype, # 3 (Type3) or 42 (Truetype)
1188 "pdf.compression": validate_int, # 0-9 compression level; 0 to disable
1189 "pdf.inheritcolor": validate_bool, # skip color setting commands
1190 # use only the 14 PDF core fonts embedded in every PDF viewing application
1191 "pdf.use14corefonts": validate_bool,
1192 "pdf.fonttype": validate_fonttype, # 3 (Type3) or 42 (Truetype)
1194 "pgf.texsystem": ["xelatex", "lualatex", "pdflatex"], # latex variant used
1195 "pgf.rcfonts": validate_bool, # use mpl's rc settings for font config
1196 "pgf.preamble": validate_string, # custom LaTeX preamble
1198 # write raster image data into the svg file
1199 "svg.image_inline": validate_bool,
1200 "svg.fonttype": ["none", "path"], # save text as text ("none") or "paths"
1201 "svg.hashsalt": validate_string_or_None,
1203 # set this when you want to generate hardcopy docstring
1204 "docstring.hardcopy": validate_bool,
1206 "path.simplify": validate_bool,
1207 "path.simplify_threshold": _range_validators["0 <= x <= 1"],
1208 "path.snap": validate_bool,
1209 "path.sketch": validate_sketch,
1210 "path.effects": validate_anylist,
1211 "agg.path.chunksize": validate_int, # 0 to disable chunking
1213 # key-mappings (multi-character mappings should be a list/tuple)
1214 "keymap.fullscreen": validate_stringlist,
1215 "keymap.home": validate_stringlist,
1216 "keymap.back": validate_stringlist,
1217 "keymap.forward": validate_stringlist,
1218 "keymap.pan": validate_stringlist,
1219 "keymap.zoom": validate_stringlist,
1220 "keymap.save": validate_stringlist,
1221 "keymap.quit": validate_stringlist,
1222 "keymap.quit_all": validate_stringlist, # e.g.: "W", "cmd+W", "Q"
1223 "keymap.grid": validate_stringlist,
1224 "keymap.grid_minor": validate_stringlist,
1225 "keymap.yscale": validate_stringlist,
1226 "keymap.xscale": validate_stringlist,
1227 "keymap.help": validate_stringlist,
1228 "keymap.copy": validate_stringlist,
1230 # Animation settings
1231 "animation.html": ["html5", "jshtml", "none"],
1232 # Limit, in MB, of size of base64 encoded animation in HTML
1233 # (i.e. IPython notebook)
1234 "animation.embed_limit": validate_float,
1235 "animation.writer": validate_string,
1236 "animation.codec": validate_string,
1237 "animation.bitrate": validate_int,
1238 # Controls image format when frames are written to disk
1239 "animation.frame_format": ["png", "jpeg", "tiff", "raw", "rgba", "ppm",
1240 "sgi", "bmp", "pbm", "svg"],
1241 # Path to ffmpeg binary. If just binary name, subprocess uses $PATH.
1242 "animation.ffmpeg_path": _validate_pathlike,
1243 # Additional arguments for ffmpeg movie writer (using pipes)
1244 "animation.ffmpeg_args": validate_stringlist,
1245 # Path to convert binary. If just binary name, subprocess uses $PATH.
1246 "animation.convert_path": _validate_pathlike,
1247 # Additional arguments for convert movie writer (using pipes)
1248 "animation.convert_args": validate_stringlist,
1250 # Classic (pre 2.0) compatibility mode
1251 # This is used for things that are hard to make backward compatible
1252 # with a sane rcParam alone. This does *not* turn on classic mode
1253 # altogether. For that use `matplotlib.style.use("classic")`.
1254 "_internal.classic_mode": validate_bool
1255}
1256_hardcoded_defaults = { # Defaults not inferred from matplotlibrc.template...
1257 # ... because they are private:
1258 "_internal.classic_mode": False,
1259 # ... because they are deprecated:
1260 # No current deprecations.
1261 # backend is handled separately when constructing rcParamsDefault.
1262}
1263_validators = {k: _convert_validator_spec(k, conv)
1264 for k, conv in _validators.items()}