| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579 |
- """
- An object-oriented plotting library.
- A procedural interface is provided by the companion pyplot module,
- which may be imported directly, e.g.::
- import matplotlib.pyplot as plt
- or using ipython::
- ipython
- at your terminal, followed by::
- In [1]: %matplotlib
- In [2]: import matplotlib.pyplot as plt
- at the ipython shell prompt.
- For the most part, direct use of the explicit object-oriented library is
- encouraged when programming; the implicit pyplot interface is primarily for
- working interactively. The exceptions to this suggestion are the pyplot
- functions `.pyplot.figure`, `.pyplot.subplot`, `.pyplot.subplots`, and
- `.pyplot.savefig`, which can greatly simplify scripting. See
- :ref:`api_interfaces` for an explanation of the tradeoffs between the implicit
- and explicit interfaces.
- Modules include:
- :mod:`matplotlib.axes`
- The `~.axes.Axes` class. Most pyplot functions are wrappers for
- `~.axes.Axes` methods. The axes module is the highest level of OO
- access to the library.
- :mod:`matplotlib.figure`
- The `.Figure` class.
- :mod:`matplotlib.artist`
- The `.Artist` base class for all classes that draw things.
- :mod:`matplotlib.lines`
- The `.Line2D` class for drawing lines and markers.
- :mod:`matplotlib.patches`
- Classes for drawing polygons.
- :mod:`matplotlib.text`
- The `.Text` and `.Annotation` classes.
- :mod:`matplotlib.image`
- The `.AxesImage` and `.FigureImage` classes.
- :mod:`matplotlib.collections`
- Classes for efficient drawing of groups of lines or polygons.
- :mod:`matplotlib.colors`
- Color specifications and making colormaps.
- :mod:`matplotlib.cm`
- Colormaps, and the `.ScalarMappable` mixin class for providing color
- mapping functionality to other classes.
- :mod:`matplotlib.ticker`
- Calculation of tick mark locations and formatting of tick labels.
- :mod:`matplotlib.backends`
- A subpackage with modules for various GUI libraries and output formats.
- The base matplotlib namespace includes:
- `~matplotlib.rcParams`
- Default configuration settings; their defaults may be overridden using
- a :file:`matplotlibrc` file.
- `~matplotlib.use`
- Setting the Matplotlib backend. This should be called before any
- figure is created, because it is not possible to switch between
- different GUI backends after that.
- The following environment variables can be used to customize the behavior:
- :envvar:`MPLBACKEND`
- This optional variable can be set to choose the Matplotlib backend. See
- :ref:`what-is-a-backend`.
- :envvar:`MPLCONFIGDIR`
- This is the directory used to store user customizations to
- Matplotlib, as well as some caches to improve performance. If
- :envvar:`MPLCONFIGDIR` is not defined, :file:`{HOME}/.config/matplotlib`
- and :file:`{HOME}/.cache/matplotlib` are used on Linux, and
- :file:`{HOME}/.matplotlib` on other platforms, if they are
- writable. Otherwise, the Python standard library's `tempfile.gettempdir`
- is used to find a base directory in which the :file:`matplotlib`
- subdirectory is created.
- Matplotlib was initially written by John D. Hunter (1968-2012) and is now
- developed and maintained by a host of others.
- Occasionally the internal documentation (python docstrings) will refer
- to MATLAB®, a registered trademark of The MathWorks, Inc.
- """
- __all__ = [
- "__bibtex__",
- "__version__",
- "__version_info__",
- "set_loglevel",
- "ExecutableNotFoundError",
- "get_configdir",
- "get_cachedir",
- "get_data_path",
- "matplotlib_fname",
- "MatplotlibDeprecationWarning",
- "RcParams",
- "rc_params",
- "rc_params_from_file",
- "rcParamsDefault",
- "rcParams",
- "rcParamsOrig",
- "defaultParams",
- "rc",
- "rcdefaults",
- "rc_file_defaults",
- "rc_file",
- "rc_context",
- "use",
- "get_backend",
- "interactive",
- "is_interactive",
- "colormaps",
- "multivar_colormaps",
- "bivar_colormaps",
- "color_sequences",
- ]
- import atexit
- from collections import namedtuple
- from collections.abc import MutableMapping
- import contextlib
- import functools
- import importlib
- import inspect
- from inspect import Parameter
- import locale
- import logging
- import os
- from pathlib import Path
- import pprint
- import re
- import shutil
- import subprocess
- import sys
- import tempfile
- from packaging.version import parse as parse_version
- # cbook must import matplotlib only within function
- # definitions, so it is safe to import from it here.
- from . import _api, _version, cbook, _docstring, rcsetup
- from matplotlib._api import MatplotlibDeprecationWarning
- from matplotlib.rcsetup import cycler # noqa: F401
- _log = logging.getLogger(__name__)
- __bibtex__ = r"""@Article{Hunter:2007,
- Author = {Hunter, J. D.},
- Title = {Matplotlib: A 2D graphics environment},
- Journal = {Computing in Science \& Engineering},
- Volume = {9},
- Number = {3},
- Pages = {90--95},
- abstract = {Matplotlib is a 2D graphics package used for Python
- for application development, interactive scripting, and
- publication-quality image generation across user
- interfaces and operating systems.},
- publisher = {IEEE COMPUTER SOC},
- year = 2007
- }"""
- # modelled after sys.version_info
- _VersionInfo = namedtuple('_VersionInfo',
- 'major, minor, micro, releaselevel, serial')
- def _parse_to_version_info(version_str):
- """
- Parse a version string to a namedtuple analogous to sys.version_info.
- See:
- https://packaging.pypa.io/en/latest/version.html#packaging.version.parse
- https://docs.python.org/3/library/sys.html#sys.version_info
- """
- v = parse_version(version_str)
- if v.pre is None and v.post is None and v.dev is None:
- return _VersionInfo(v.major, v.minor, v.micro, 'final', 0)
- elif v.dev is not None:
- return _VersionInfo(v.major, v.minor, v.micro, 'alpha', v.dev)
- elif v.pre is not None:
- releaselevel = {
- 'a': 'alpha',
- 'b': 'beta',
- 'rc': 'candidate'}.get(v.pre[0], 'alpha')
- return _VersionInfo(v.major, v.minor, v.micro, releaselevel, v.pre[1])
- else:
- # fallback for v.post: guess-next-dev scheme from setuptools_scm
- return _VersionInfo(v.major, v.minor, v.micro + 1, 'alpha', v.post)
- def _get_version():
- """Return the version string used for __version__."""
- # Only shell out to a git subprocess if really needed, i.e. when we are in
- # a matplotlib git repo but not in a shallow clone, such as those used by
- # CI, as the latter would trigger a warning from setuptools_scm.
- root = Path(__file__).resolve().parents[2]
- if ((root / ".matplotlib-repo").exists()
- and (root / ".git").exists()
- and not (root / ".git/shallow").exists()):
- try:
- import setuptools_scm
- except ImportError:
- pass
- else:
- return setuptools_scm.get_version(
- root=root,
- dist_name="matplotlib",
- version_scheme="release-branch-semver",
- local_scheme="node-and-date",
- fallback_version=_version.version,
- )
- # Get the version from the _version.py file if not in repo or setuptools_scm is
- # unavailable.
- return _version.version
- @_api.caching_module_getattr
- class __getattr__:
- __version__ = property(lambda self: _get_version())
- __version_info__ = property(
- lambda self: _parse_to_version_info(self.__version__))
- def _check_versions():
- # Quickfix to ensure Microsoft Visual C++ redistributable
- # DLLs are loaded before importing kiwisolver
- from . import ft2font # noqa: F401
- for modname, minver in [
- ("cycler", "0.10"),
- ("dateutil", "2.7"),
- ("kiwisolver", "1.3.1"),
- ("numpy", "1.23"),
- ("pyparsing", "2.3.1"),
- ]:
- module = importlib.import_module(modname)
- if parse_version(module.__version__) < parse_version(minver):
- raise ImportError(f"Matplotlib requires {modname}>={minver}; "
- f"you have {module.__version__}")
- _check_versions()
- # The decorator ensures this always returns the same handler (and it is only
- # attached once).
- @functools.cache
- def _ensure_handler():
- """
- The first time this function is called, attach a `StreamHandler` using the
- same format as `logging.basicConfig` to the Matplotlib root logger.
- Return this handler every time this function is called.
- """
- handler = logging.StreamHandler()
- handler.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
- _log.addHandler(handler)
- return handler
- def set_loglevel(level):
- """
- Configure Matplotlib's logging levels.
- Matplotlib uses the standard library `logging` framework under the root
- logger 'matplotlib'. This is a helper function to:
- - set Matplotlib's root logger level
- - set the root logger handler's level, creating the handler
- if it does not exist yet
- Typically, one should call ``set_loglevel("info")`` or
- ``set_loglevel("debug")`` to get additional debugging information.
- Users or applications that are installing their own logging handlers
- may want to directly manipulate ``logging.getLogger('matplotlib')`` rather
- than use this function.
- Parameters
- ----------
- level : {"notset", "debug", "info", "warning", "error", "critical"}
- The log level of the handler.
- Notes
- -----
- The first time this function is called, an additional handler is attached
- to Matplotlib's root handler; this handler is reused every time and this
- function simply manipulates the logger and handler's level.
- """
- _log.setLevel(level.upper())
- _ensure_handler().setLevel(level.upper())
- def _logged_cached(fmt, func=None):
- """
- Decorator that logs a function's return value, and memoizes that value.
- After ::
- @_logged_cached(fmt)
- def func(): ...
- the first call to *func* will log its return value at the DEBUG level using
- %-format string *fmt*, and memoize it; later calls to *func* will directly
- return that value.
- """
- if func is None: # Return the actual decorator.
- return functools.partial(_logged_cached, fmt)
- called = False
- ret = None
- @functools.wraps(func)
- def wrapper(**kwargs):
- nonlocal called, ret
- if not called:
- ret = func(**kwargs)
- called = True
- _log.debug(fmt, ret)
- return ret
- return wrapper
- _ExecInfo = namedtuple("_ExecInfo", "executable raw_version version")
- class ExecutableNotFoundError(FileNotFoundError):
- """
- Error raised when an executable that Matplotlib optionally
- depends on can't be found.
- """
- pass
- @functools.cache
- def _get_executable_info(name):
- """
- Get the version of some executable that Matplotlib optionally depends on.
- .. warning::
- The list of executables that this function supports is set according to
- Matplotlib's internal needs, and may change without notice.
- Parameters
- ----------
- name : str
- The executable to query. The following values are currently supported:
- "dvipng", "gs", "inkscape", "magick", "pdftocairo", "pdftops". This
- list is subject to change without notice.
- Returns
- -------
- tuple
- A namedtuple with fields ``executable`` (`str`) and ``version``
- (`packaging.Version`, or ``None`` if the version cannot be determined).
- Raises
- ------
- ExecutableNotFoundError
- If the executable is not found or older than the oldest version
- supported by Matplotlib. For debugging purposes, it is also
- possible to "hide" an executable from Matplotlib by adding it to the
- :envvar:`_MPLHIDEEXECUTABLES` environment variable (a comma-separated
- list), which must be set prior to any calls to this function.
- ValueError
- If the executable is not one that we know how to query.
- """
- def impl(args, regex, min_ver=None, ignore_exit_code=False):
- # Execute the subprocess specified by args; capture stdout and stderr.
- # Search for a regex match in the output; if the match succeeds, the
- # first group of the match is the version.
- # Return an _ExecInfo if the executable exists, and has a version of
- # at least min_ver (if set); else, raise ExecutableNotFoundError.
- try:
- output = subprocess.check_output(
- args, stderr=subprocess.STDOUT,
- text=True, errors="replace", timeout=30)
- except subprocess.CalledProcessError as _cpe:
- if ignore_exit_code:
- output = _cpe.output
- else:
- raise ExecutableNotFoundError(str(_cpe)) from _cpe
- except subprocess.TimeoutExpired as _te:
- msg = f"Timed out running {cbook._pformat_subprocess(args)}"
- raise ExecutableNotFoundError(msg) from _te
- except OSError as _ose:
- raise ExecutableNotFoundError(str(_ose)) from _ose
- match = re.search(regex, output)
- if match:
- raw_version = match.group(1)
- version = parse_version(raw_version)
- if min_ver is not None and version < parse_version(min_ver):
- raise ExecutableNotFoundError(
- f"You have {args[0]} version {version} but the minimum "
- f"version supported by Matplotlib is {min_ver}")
- return _ExecInfo(args[0], raw_version, version)
- else:
- raise ExecutableNotFoundError(
- f"Failed to determine the version of {args[0]} from "
- f"{' '.join(args)}, which output {output}")
- if name in os.environ.get("_MPLHIDEEXECUTABLES", "").split(","):
- raise ExecutableNotFoundError(f"{name} was hidden")
- if name == "dvipng":
- return impl(["dvipng", "-version"], "(?m)^dvipng(?: .*)? (.+)", "1.6")
- elif name == "gs":
- execs = (["gswin32c", "gswin64c", "mgs", "gs"] # "mgs" for miktex.
- if sys.platform == "win32" else
- ["gs"])
- for e in execs:
- try:
- return impl([e, "--version"], "(.*)", "9")
- except ExecutableNotFoundError:
- pass
- message = "Failed to find a Ghostscript installation"
- raise ExecutableNotFoundError(message)
- elif name == "inkscape":
- try:
- # Try headless option first (needed for Inkscape version < 1.0):
- return impl(["inkscape", "--without-gui", "-V"],
- "Inkscape ([^ ]*)")
- except ExecutableNotFoundError:
- pass # Suppress exception chaining.
- # If --without-gui is not accepted, we may be using Inkscape >= 1.0 so
- # try without it:
- return impl(["inkscape", "-V"], "Inkscape ([^ ]*)")
- elif name == "magick":
- if sys.platform == "win32":
- # Check the registry to avoid confusing ImageMagick's convert with
- # Windows's builtin convert.exe.
- import winreg
- binpath = ""
- for flag in [0, winreg.KEY_WOW64_32KEY, winreg.KEY_WOW64_64KEY]:
- try:
- with winreg.OpenKeyEx(
- winreg.HKEY_LOCAL_MACHINE,
- r"Software\Imagemagick\Current",
- 0, winreg.KEY_QUERY_VALUE | flag) as hkey:
- binpath = winreg.QueryValueEx(hkey, "BinPath")[0]
- except OSError:
- pass
- path = None
- if binpath:
- for name in ["convert.exe", "magick.exe"]:
- candidate = Path(binpath, name)
- if candidate.exists():
- path = str(candidate)
- break
- if path is None:
- raise ExecutableNotFoundError(
- "Failed to find an ImageMagick installation")
- else:
- path = "convert"
- info = impl([path, "--version"], r"^Version: ImageMagick (\S*)")
- if info.raw_version == "7.0.10-34":
- # https://github.com/ImageMagick/ImageMagick/issues/2720
- raise ExecutableNotFoundError(
- f"You have ImageMagick {info.version}, which is unsupported")
- return info
- elif name == "pdftocairo":
- return impl(["pdftocairo", "-v"], "pdftocairo version (.*)")
- elif name == "pdftops":
- info = impl(["pdftops", "-v"], "^pdftops version (.*)",
- ignore_exit_code=True)
- if info and not (
- 3 <= info.version.major or
- # poppler version numbers.
- parse_version("0.9") <= info.version < parse_version("1.0")):
- raise ExecutableNotFoundError(
- f"You have pdftops version {info.version} but the minimum "
- f"version supported by Matplotlib is 3.0")
- return info
- else:
- raise ValueError(f"Unknown executable: {name!r}")
- def _get_xdg_config_dir():
- """
- Return the XDG configuration directory, according to the XDG base
- directory spec:
- https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
- """
- return os.environ.get('XDG_CONFIG_HOME') or str(Path.home() / ".config")
- def _get_xdg_cache_dir():
- """
- Return the XDG cache directory, according to the XDG base directory spec:
- https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
- """
- return os.environ.get('XDG_CACHE_HOME') or str(Path.home() / ".cache")
- def _get_config_or_cache_dir(xdg_base_getter):
- configdir = os.environ.get('MPLCONFIGDIR')
- if configdir:
- configdir = Path(configdir)
- elif sys.platform.startswith(('linux', 'freebsd')):
- # Only call _xdg_base_getter here so that MPLCONFIGDIR is tried first,
- # as _xdg_base_getter can throw.
- configdir = Path(xdg_base_getter(), "matplotlib")
- else:
- configdir = Path.home() / ".matplotlib"
- # Resolve the path to handle potential issues with inaccessible symlinks.
- configdir = configdir.resolve()
- try:
- configdir.mkdir(parents=True, exist_ok=True)
- except OSError as exc:
- _log.warning("mkdir -p failed for path %s: %s", configdir, exc)
- else:
- if os.access(str(configdir), os.W_OK) and configdir.is_dir():
- return str(configdir)
- _log.warning("%s is not a writable directory", configdir)
- # If the config or cache directory cannot be created or is not a writable
- # directory, create a temporary one.
- try:
- tmpdir = tempfile.mkdtemp(prefix="matplotlib-")
- except OSError as exc:
- raise OSError(
- f"Matplotlib requires access to a writable cache directory, but there "
- f"was an issue with the default path ({configdir}), and a temporary "
- f"directory could not be created; set the MPLCONFIGDIR environment "
- f"variable to a writable directory") from exc
- os.environ["MPLCONFIGDIR"] = tmpdir
- atexit.register(shutil.rmtree, tmpdir)
- _log.warning(
- "Matplotlib created a temporary cache directory at %s because there was "
- "an issue with the default path (%s); it is highly recommended to set the "
- "MPLCONFIGDIR environment variable to a writable directory, in particular to "
- "speed up the import of Matplotlib and to better support multiprocessing.",
- tmpdir, configdir)
- return tmpdir
- @_logged_cached('CONFIGDIR=%s')
- def get_configdir():
- """
- Return the string path of the configuration directory.
- The directory is chosen as follows:
- 1. If the MPLCONFIGDIR environment variable is supplied, choose that.
- 2. On Linux, follow the XDG specification and look first in
- ``$XDG_CONFIG_HOME``, if defined, or ``$HOME/.config``. On other
- platforms, choose ``$HOME/.matplotlib``.
- 3. If the chosen directory exists and is writable, use that as the
- configuration directory.
- 4. Else, create a temporary directory, and use it as the configuration
- directory.
- """
- return _get_config_or_cache_dir(_get_xdg_config_dir)
- @_logged_cached('CACHEDIR=%s')
- def get_cachedir():
- """
- Return the string path of the cache directory.
- The procedure used to find the directory is the same as for
- `get_configdir`, except using ``$XDG_CACHE_HOME``/``$HOME/.cache`` instead.
- """
- return _get_config_or_cache_dir(_get_xdg_cache_dir)
- @_logged_cached('matplotlib data path: %s')
- def get_data_path():
- """Return the path to Matplotlib data."""
- return str(Path(__file__).with_name("mpl-data"))
- def matplotlib_fname():
- """
- Get the location of the config file.
- The file location is determined in the following order
- - ``$PWD/matplotlibrc``
- - ``$MATPLOTLIBRC`` if it is not a directory
- - ``$MATPLOTLIBRC/matplotlibrc``
- - ``$MPLCONFIGDIR/matplotlibrc``
- - On Linux,
- - ``$XDG_CONFIG_HOME/matplotlib/matplotlibrc`` (if ``$XDG_CONFIG_HOME``
- is defined)
- - or ``$HOME/.config/matplotlib/matplotlibrc`` (if ``$XDG_CONFIG_HOME``
- is not defined)
- - On other platforms,
- - ``$HOME/.matplotlib/matplotlibrc`` if ``$HOME`` is defined
- - Lastly, it looks in ``$MATPLOTLIBDATA/matplotlibrc``, which should always
- exist.
- """
- def gen_candidates():
- # rely on down-stream code to make absolute. This protects us
- # from having to directly get the current working directory
- # which can fail if the user has ended up with a cwd that is
- # non-existent.
- yield 'matplotlibrc'
- try:
- matplotlibrc = os.environ['MATPLOTLIBRC']
- except KeyError:
- pass
- else:
- yield matplotlibrc
- yield os.path.join(matplotlibrc, 'matplotlibrc')
- yield os.path.join(get_configdir(), 'matplotlibrc')
- yield os.path.join(get_data_path(), 'matplotlibrc')
- for fname in gen_candidates():
- if os.path.exists(fname) and not os.path.isdir(fname):
- return fname
- raise RuntimeError("Could not find matplotlibrc file; your Matplotlib "
- "install is broken")
- # rcParams deprecated and automatically mapped to another key.
- # Values are tuples of (version, new_name, f_old2new, f_new2old).
- _deprecated_map = {}
- # rcParams deprecated; some can manually be mapped to another key.
- # Values are tuples of (version, new_name_or_None).
- _deprecated_ignore_map = {}
- # rcParams deprecated; can use None to suppress warnings; remain actually
- # listed in the rcParams.
- # Values are tuples of (version,)
- _deprecated_remain_as_none = {}
- @_docstring.Substitution(
- "\n".join(map("- {}".format, sorted(rcsetup._validators, key=str.lower)))
- )
- class RcParams(MutableMapping, dict):
- """
- A dict-like key-value store for config parameters, including validation.
- Validating functions are defined and associated with rc parameters in
- :mod:`matplotlib.rcsetup`.
- The list of rcParams is:
- %s
- See Also
- --------
- :ref:`customizing-with-matplotlibrc-files`
- """
- validate = rcsetup._validators
- # validate values on the way in
- def __init__(self, *args, **kwargs):
- self.update(*args, **kwargs)
- def _set(self, key, val):
- """
- Directly write data bypassing deprecation and validation logic.
- Notes
- -----
- As end user or downstream library you almost always should use
- ``rcParams[key] = val`` and not ``_set()``.
- There are only very few special cases that need direct data access.
- These cases previously used ``dict.__setitem__(rcParams, key, val)``,
- which is now deprecated and replaced by ``rcParams._set(key, val)``.
- Even though private, we guarantee API stability for ``rcParams._set``,
- i.e. it is subject to Matplotlib's API and deprecation policy.
- :meta public:
- """
- dict.__setitem__(self, key, val)
- def _get(self, key):
- """
- Directly read data bypassing deprecation, backend and validation
- logic.
- Notes
- -----
- As end user or downstream library you almost always should use
- ``val = rcParams[key]`` and not ``_get()``.
- There are only very few special cases that need direct data access.
- These cases previously used ``dict.__getitem__(rcParams, key, val)``,
- which is now deprecated and replaced by ``rcParams._get(key)``.
- Even though private, we guarantee API stability for ``rcParams._get``,
- i.e. it is subject to Matplotlib's API and deprecation policy.
- :meta public:
- """
- return dict.__getitem__(self, key)
- def _update_raw(self, other_params):
- """
- Directly update the data from *other_params*, bypassing deprecation,
- backend and validation logic on both sides.
- This ``rcParams._update_raw(params)`` replaces the previous pattern
- ``dict.update(rcParams, params)``.
- Parameters
- ----------
- other_params : dict or `.RcParams`
- The input mapping from which to update.
- """
- if isinstance(other_params, RcParams):
- other_params = dict.items(other_params)
- dict.update(self, other_params)
- def _ensure_has_backend(self):
- """
- Ensure that a "backend" entry exists.
- Normally, the default matplotlibrc file contains *no* entry for "backend" (the
- corresponding line starts with ##, not #; we fill in _auto_backend_sentinel
- in that case. However, packagers can set a different default backend
- (resulting in a normal `#backend: foo` line) in which case we should *not*
- fill in _auto_backend_sentinel.
- """
- dict.setdefault(self, "backend", rcsetup._auto_backend_sentinel)
- def __setitem__(self, key, val):
- try:
- if key in _deprecated_map:
- version, alt_key, alt_val, inverse_alt = _deprecated_map[key]
- _api.warn_deprecated(
- version, name=key, obj_type="rcparam", alternative=alt_key)
- key = alt_key
- val = alt_val(val)
- elif key in _deprecated_remain_as_none and val is not None:
- version, = _deprecated_remain_as_none[key]
- _api.warn_deprecated(version, name=key, obj_type="rcparam")
- elif key in _deprecated_ignore_map:
- version, alt_key = _deprecated_ignore_map[key]
- _api.warn_deprecated(
- version, name=key, obj_type="rcparam", alternative=alt_key)
- return
- elif key == 'backend':
- if val is rcsetup._auto_backend_sentinel:
- if 'backend' in self:
- return
- try:
- cval = self.validate[key](val)
- except ValueError as ve:
- raise ValueError(f"Key {key}: {ve}") from None
- self._set(key, cval)
- except KeyError as err:
- raise KeyError(
- f"{key} is not a valid rc parameter (see rcParams.keys() for "
- f"a list of valid parameters)") from err
- def __getitem__(self, key):
- if key in _deprecated_map:
- version, alt_key, alt_val, inverse_alt = _deprecated_map[key]
- _api.warn_deprecated(
- version, name=key, obj_type="rcparam", alternative=alt_key)
- return inverse_alt(self._get(alt_key))
- elif key in _deprecated_ignore_map:
- version, alt_key = _deprecated_ignore_map[key]
- _api.warn_deprecated(
- version, name=key, obj_type="rcparam", alternative=alt_key)
- return self._get(alt_key) if alt_key else None
- # In theory, this should only ever be used after the global rcParams
- # has been set up, but better be safe e.g. in presence of breakpoints.
- elif key == "backend" and self is globals().get("rcParams"):
- val = self._get(key)
- if val is rcsetup._auto_backend_sentinel:
- from matplotlib import pyplot as plt
- plt.switch_backend(rcsetup._auto_backend_sentinel)
- return self._get(key)
- def _get_backend_or_none(self):
- """Get the requested backend, if any, without triggering resolution."""
- backend = self._get("backend")
- return None if backend is rcsetup._auto_backend_sentinel else backend
- def __repr__(self):
- class_name = self.__class__.__name__
- indent = len(class_name) + 1
- with _api.suppress_matplotlib_deprecation_warning():
- repr_split = pprint.pformat(dict(self), indent=1,
- width=80 - indent).split('\n')
- repr_indented = ('\n' + ' ' * indent).join(repr_split)
- return f'{class_name}({repr_indented})'
- def __str__(self):
- return '\n'.join(map('{0[0]}: {0[1]}'.format, sorted(self.items())))
- def __iter__(self):
- """Yield sorted list of keys."""
- with _api.suppress_matplotlib_deprecation_warning():
- yield from sorted(dict.__iter__(self))
- def __len__(self):
- return dict.__len__(self)
- def find_all(self, pattern):
- """
- Return the subset of this RcParams dictionary whose keys match,
- using :func:`re.search`, the given ``pattern``.
- .. note::
- Changes to the returned dictionary are *not* propagated to
- the parent RcParams dictionary.
- """
- pattern_re = re.compile(pattern)
- return RcParams((key, value)
- for key, value in self.items()
- if pattern_re.search(key))
- def copy(self):
- """Copy this RcParams instance."""
- rccopy = RcParams()
- for k in self: # Skip deprecations and revalidation.
- rccopy._set(k, self._get(k))
- return rccopy
- def rc_params(fail_on_error=False):
- """Construct a `RcParams` instance from the default Matplotlib rc file."""
- return rc_params_from_file(matplotlib_fname(), fail_on_error)
- @functools.cache
- def _get_ssl_context():
- try:
- import certifi
- except ImportError:
- _log.debug("Could not import certifi.")
- return None
- import ssl
- return ssl.create_default_context(cafile=certifi.where())
- @contextlib.contextmanager
- def _open_file_or_url(fname):
- if (isinstance(fname, str)
- and fname.startswith(('http://', 'https://', 'ftp://', 'file:'))):
- import urllib.request
- ssl_ctx = _get_ssl_context()
- if ssl_ctx is None:
- _log.debug(
- "Could not get certifi ssl context, https may not work."
- )
- with urllib.request.urlopen(fname, context=ssl_ctx) as f:
- yield (line.decode('utf-8') for line in f)
- else:
- fname = os.path.expanduser(fname)
- with open(fname, encoding='utf-8') as f:
- yield f
- def _rc_params_in_file(fname, transform=lambda x: x, fail_on_error=False):
- """
- Construct a `RcParams` instance from file *fname*.
- Unlike `rc_params_from_file`, the configuration class only contains the
- parameters specified in the file (i.e. default values are not filled in).
- Parameters
- ----------
- fname : path-like
- The loaded file.
- transform : callable, default: the identity function
- A function called on each individual line of the file to transform it,
- before further parsing.
- fail_on_error : bool, default: False
- Whether invalid entries should result in an exception or a warning.
- """
- import matplotlib as mpl
- rc_temp = {}
- with _open_file_or_url(fname) as fd:
- try:
- for line_no, line in enumerate(fd, 1):
- line = transform(line)
- strippedline = cbook._strip_comment(line)
- if not strippedline:
- continue
- tup = strippedline.split(':', 1)
- if len(tup) != 2:
- _log.warning('Missing colon in file %r, line %d (%r)',
- fname, line_no, line.rstrip('\n'))
- continue
- key, val = tup
- key = key.strip()
- val = val.strip()
- if val.startswith('"') and val.endswith('"'):
- val = val[1:-1] # strip double quotes
- if key in rc_temp:
- _log.warning('Duplicate key in file %r, line %d (%r)',
- fname, line_no, line.rstrip('\n'))
- rc_temp[key] = (val, line, line_no)
- except UnicodeDecodeError:
- _log.warning('Cannot decode configuration file %r as utf-8.',
- fname)
- raise
- config = RcParams()
- for key, (val, line, line_no) in rc_temp.items():
- if key in rcsetup._validators:
- if fail_on_error:
- config[key] = val # try to convert to proper type or raise
- else:
- try:
- config[key] = val # try to convert to proper type or skip
- except Exception as msg:
- _log.warning('Bad value in file %r, line %d (%r): %s',
- fname, line_no, line.rstrip('\n'), msg)
- elif key in _deprecated_ignore_map:
- version, alt_key = _deprecated_ignore_map[key]
- _api.warn_deprecated(
- version, name=key, alternative=alt_key, obj_type='rcparam',
- addendum="Please update your matplotlibrc.")
- else:
- # __version__ must be looked up as an attribute to trigger the
- # module-level __getattr__.
- version = ('main' if '.post' in mpl.__version__
- else f'v{mpl.__version__}')
- _log.warning("""
- Bad key %(key)s in file %(fname)s, line %(line_no)s (%(line)r)
- You probably need to get an updated matplotlibrc file from
- https://github.com/matplotlib/matplotlib/blob/%(version)s/lib/matplotlib/mpl-data/matplotlibrc
- or from the matplotlib source distribution""",
- dict(key=key, fname=fname, line_no=line_no,
- line=line.rstrip('\n'), version=version))
- return config
- def rc_params_from_file(fname, fail_on_error=False, use_default_template=True):
- """
- Construct a `RcParams` from file *fname*.
- Parameters
- ----------
- fname : str or path-like
- A file with Matplotlib rc settings.
- fail_on_error : bool
- If True, raise an error when the parser fails to convert a parameter.
- use_default_template : bool
- If True, initialize with default parameters before updating with those
- in the given file. If False, the configuration class only contains the
- parameters specified in the file. (Useful for updating dicts.)
- """
- config_from_file = _rc_params_in_file(fname, fail_on_error=fail_on_error)
- if not use_default_template:
- return config_from_file
- with _api.suppress_matplotlib_deprecation_warning():
- config = RcParams({**rcParamsDefault, **config_from_file})
- if "".join(config['text.latex.preamble']):
- _log.info("""
- *****************************************************************
- You have the following UNSUPPORTED LaTeX preamble customizations:
- %s
- Please do not ask for support with these customizations active.
- *****************************************************************
- """, '\n'.join(config['text.latex.preamble']))
- _log.debug('loaded rc file %s', fname)
- return config
- rcParamsDefault = _rc_params_in_file(
- cbook._get_data_path("matplotlibrc"),
- # Strip leading comment.
- transform=lambda line: line[1:] if line.startswith("#") else line,
- fail_on_error=True)
- rcParamsDefault._update_raw(rcsetup._hardcoded_defaults)
- rcParamsDefault._ensure_has_backend()
- rcParams = RcParams() # The global instance.
- rcParams._update_raw(rcParamsDefault)
- rcParams._update_raw(_rc_params_in_file(matplotlib_fname()))
- rcParamsOrig = rcParams.copy()
- with _api.suppress_matplotlib_deprecation_warning():
- # This also checks that all rcParams are indeed listed in the template.
- # Assigning to rcsetup.defaultParams is left only for backcompat.
- defaultParams = rcsetup.defaultParams = {
- # We want to resolve deprecated rcParams, but not backend...
- key: [(rcsetup._auto_backend_sentinel if key == "backend" else
- rcParamsDefault[key]),
- validator]
- for key, validator in rcsetup._validators.items()}
- if rcParams['axes.formatter.use_locale']:
- locale.setlocale(locale.LC_ALL, '')
- def rc(group, **kwargs):
- """
- Set the current `.rcParams`. *group* is the grouping for the rc, e.g.,
- for ``lines.linewidth`` the group is ``lines``, for
- ``axes.facecolor``, the group is ``axes``, and so on. Group may
- also be a list or tuple of group names, e.g., (*xtick*, *ytick*).
- *kwargs* is a dictionary attribute name/value pairs, e.g.,::
- rc('lines', linewidth=2, color='r')
- sets the current `.rcParams` and is equivalent to::
- rcParams['lines.linewidth'] = 2
- rcParams['lines.color'] = 'r'
- The following aliases are available to save typing for interactive users:
- ===== =================
- Alias Property
- ===== =================
- 'lw' 'linewidth'
- 'ls' 'linestyle'
- 'c' 'color'
- 'fc' 'facecolor'
- 'ec' 'edgecolor'
- 'mew' 'markeredgewidth'
- 'aa' 'antialiased'
- ===== =================
- Thus you could abbreviate the above call as::
- rc('lines', lw=2, c='r')
- Note you can use python's kwargs dictionary facility to store
- dictionaries of default parameters. e.g., you can customize the
- font rc as follows::
- font = {'family' : 'monospace',
- 'weight' : 'bold',
- 'size' : 'larger'}
- rc('font', **font) # pass in the font dict as kwargs
- This enables you to easily switch between several configurations. Use
- ``matplotlib.style.use('default')`` or :func:`~matplotlib.rcdefaults` to
- restore the default `.rcParams` after changes.
- Notes
- -----
- Similar functionality is available by using the normal dict interface, i.e.
- ``rcParams.update({"lines.linewidth": 2, ...})`` (but ``rcParams.update``
- does not support abbreviations or grouping).
- """
- aliases = {
- 'lw': 'linewidth',
- 'ls': 'linestyle',
- 'c': 'color',
- 'fc': 'facecolor',
- 'ec': 'edgecolor',
- 'mew': 'markeredgewidth',
- 'aa': 'antialiased',
- }
- if isinstance(group, str):
- group = (group,)
- for g in group:
- for k, v in kwargs.items():
- name = aliases.get(k) or k
- key = f'{g}.{name}'
- try:
- rcParams[key] = v
- except KeyError as err:
- raise KeyError(('Unrecognized key "%s" for group "%s" and '
- 'name "%s"') % (key, g, name)) from err
- def rcdefaults():
- """
- Restore the `.rcParams` from Matplotlib's internal default style.
- Style-blacklisted `.rcParams` (defined in
- ``matplotlib.style.core.STYLE_BLACKLIST``) are not updated.
- See Also
- --------
- matplotlib.rc_file_defaults
- Restore the `.rcParams` from the rc file originally loaded by
- Matplotlib.
- matplotlib.style.use
- Use a specific style file. Call ``style.use('default')`` to restore
- the default style.
- """
- # Deprecation warnings were already handled when creating rcParamsDefault,
- # no need to reemit them here.
- with _api.suppress_matplotlib_deprecation_warning():
- from .style.core import STYLE_BLACKLIST
- rcParams.clear()
- rcParams.update({k: v for k, v in rcParamsDefault.items()
- if k not in STYLE_BLACKLIST})
- def rc_file_defaults():
- """
- Restore the `.rcParams` from the original rc file loaded by Matplotlib.
- Style-blacklisted `.rcParams` (defined in
- ``matplotlib.style.core.STYLE_BLACKLIST``) are not updated.
- """
- # Deprecation warnings were already handled when creating rcParamsOrig, no
- # need to reemit them here.
- with _api.suppress_matplotlib_deprecation_warning():
- from .style.core import STYLE_BLACKLIST
- rcParams.update({k: rcParamsOrig[k] for k in rcParamsOrig
- if k not in STYLE_BLACKLIST})
- def rc_file(fname, *, use_default_template=True):
- """
- Update `.rcParams` from file.
- Style-blacklisted `.rcParams` (defined in
- ``matplotlib.style.core.STYLE_BLACKLIST``) are not updated.
- Parameters
- ----------
- fname : str or path-like
- A file with Matplotlib rc settings.
- use_default_template : bool
- If True, initialize with default parameters before updating with those
- in the given file. If False, the current configuration persists
- and only the parameters specified in the file are updated.
- """
- # Deprecation warnings were already handled in rc_params_from_file, no need
- # to reemit them here.
- with _api.suppress_matplotlib_deprecation_warning():
- from .style.core import STYLE_BLACKLIST
- rc_from_file = rc_params_from_file(
- fname, use_default_template=use_default_template)
- rcParams.update({k: rc_from_file[k] for k in rc_from_file
- if k not in STYLE_BLACKLIST})
- @contextlib.contextmanager
- def rc_context(rc=None, fname=None):
- """
- Return a context manager for temporarily changing rcParams.
- The :rc:`backend` will not be reset by the context manager.
- rcParams changed both through the context manager invocation and
- in the body of the context will be reset on context exit.
- Parameters
- ----------
- rc : dict
- The rcParams to temporarily set.
- fname : str or path-like
- A file with Matplotlib rc settings. If both *fname* and *rc* are given,
- settings from *rc* take precedence.
- See Also
- --------
- :ref:`customizing-with-matplotlibrc-files`
- Examples
- --------
- Passing explicit values via a dict::
- with mpl.rc_context({'interactive': False}):
- fig, ax = plt.subplots()
- ax.plot(range(3), range(3))
- fig.savefig('example.png')
- plt.close(fig)
- Loading settings from a file::
- with mpl.rc_context(fname='print.rc'):
- plt.plot(x, y) # uses 'print.rc'
- Setting in the context body::
- with mpl.rc_context():
- # will be reset
- mpl.rcParams['lines.linewidth'] = 5
- plt.plot(x, y)
- """
- orig = dict(rcParams.copy())
- del orig['backend']
- try:
- if fname:
- rc_file(fname)
- if rc:
- rcParams.update(rc)
- yield
- finally:
- rcParams._update_raw(orig) # Revert to the original rcs.
- def use(backend, *, force=True):
- """
- Select the backend used for rendering and GUI integration.
- If pyplot is already imported, `~matplotlib.pyplot.switch_backend` is used
- and if the new backend is different than the current backend, all Figures
- will be closed.
- Parameters
- ----------
- backend : str
- The backend to switch to. This can either be one of the standard
- backend names, which are case-insensitive:
- - interactive backends:
- GTK3Agg, GTK3Cairo, GTK4Agg, GTK4Cairo, MacOSX, nbAgg, notebook, QtAgg,
- QtCairo, TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo, Qt5Agg, Qt5Cairo
- - non-interactive backends:
- agg, cairo, pdf, pgf, ps, svg, template
- or a string of the form: ``module://my.module.name``.
- notebook is a synonym for nbAgg.
- Switching to an interactive backend is not possible if an unrelated
- event loop has already been started (e.g., switching to GTK3Agg if a
- TkAgg window has already been opened). Switching to a non-interactive
- backend is always possible.
- force : bool, default: True
- If True (the default), raise an `ImportError` if the backend cannot be
- set up (either because it fails to import, or because an incompatible
- GUI interactive framework is already running); if False, silently
- ignore the failure.
- See Also
- --------
- :ref:`backends`
- matplotlib.get_backend
- matplotlib.pyplot.switch_backend
- """
- name = rcsetup.validate_backend(backend)
- # don't (prematurely) resolve the "auto" backend setting
- if rcParams._get_backend_or_none() == name:
- # Nothing to do if the requested backend is already set
- pass
- else:
- # if pyplot is not already imported, do not import it. Doing
- # so may trigger a `plt.switch_backend` to the _default_ backend
- # before we get a chance to change to the one the user just requested
- plt = sys.modules.get('matplotlib.pyplot')
- # if pyplot is imported, then try to change backends
- if plt is not None:
- try:
- # we need this import check here to re-raise if the
- # user does not have the libraries to support their
- # chosen backend installed.
- plt.switch_backend(name)
- except ImportError:
- if force:
- raise
- # if we have not imported pyplot, then we can set the rcParam
- # value which will be respected when the user finally imports
- # pyplot
- else:
- rcParams['backend'] = backend
- # if the user has asked for a given backend, do not helpfully
- # fallback
- rcParams['backend_fallback'] = False
- if os.environ.get('MPLBACKEND'):
- rcParams['backend'] = os.environ.get('MPLBACKEND')
- def get_backend(*, auto_select=True):
- """
- Return the name of the current backend.
- Parameters
- ----------
- auto_select : bool, default: True
- Whether to trigger backend resolution if no backend has been
- selected so far. If True, this ensures that a valid backend
- is returned. If False, this returns None if no backend has been
- selected so far.
- .. versionadded:: 3.10
- .. admonition:: Provisional
- The *auto_select* flag is provisional. It may be changed or removed
- without prior warning.
- See Also
- --------
- matplotlib.use
- """
- if auto_select:
- return rcParams['backend']
- else:
- backend = rcParams._get('backend')
- if backend is rcsetup._auto_backend_sentinel:
- return None
- else:
- return backend
- def interactive(b):
- """
- Set whether to redraw after every plotting command (e.g. `.pyplot.xlabel`).
- """
- rcParams['interactive'] = b
- def is_interactive():
- """
- Return whether to redraw after every plotting command.
- .. note::
- This function is only intended for use in backends. End users should
- use `.pyplot.isinteractive` instead.
- """
- return rcParams['interactive']
- def _val_or_rc(val, rc_name):
- """
- If *val* is None, return ``mpl.rcParams[rc_name]``, otherwise return val.
- """
- return val if val is not None else rcParams[rc_name]
- def _init_tests():
- # The version of FreeType to install locally for running the tests. This must match
- # the value in `meson.build`.
- LOCAL_FREETYPE_VERSION = '2.6.1'
- from matplotlib import ft2font
- if (ft2font.__freetype_version__ != LOCAL_FREETYPE_VERSION or
- ft2font.__freetype_build_type__ != 'local'):
- _log.warning(
- "Matplotlib is not built with the correct FreeType version to run tests. "
- "Rebuild without setting system-freetype=true in Meson setup options. "
- "Expect many image comparison failures below. "
- "Expected freetype version %s. "
- "Found freetype version %s. "
- "Freetype build type is %slocal.",
- LOCAL_FREETYPE_VERSION,
- ft2font.__freetype_version__,
- "" if ft2font.__freetype_build_type__ == 'local' else "not ")
- def _replacer(data, value):
- """
- Either returns ``data[value]`` or passes ``data`` back, converts either to
- a sequence.
- """
- try:
- # if key isn't a string don't bother
- if isinstance(value, str):
- # try to use __getitem__
- value = data[value]
- except Exception:
- # key does not exist, silently fall back to key
- pass
- return cbook.sanitize_sequence(value)
- def _label_from_arg(y, default_name):
- try:
- return y.name
- except AttributeError:
- if isinstance(default_name, str):
- return default_name
- return None
- def _add_data_doc(docstring, replace_names):
- """
- Add documentation for a *data* field to the given docstring.
- Parameters
- ----------
- docstring : str
- The input docstring.
- replace_names : list of str or None
- The list of parameter names which arguments should be replaced by
- ``data[name]`` (if ``data[name]`` does not throw an exception). If
- None, replacement is attempted for all arguments.
- Returns
- -------
- str
- The augmented docstring.
- """
- if (docstring is None
- or replace_names is not None and len(replace_names) == 0):
- return docstring
- docstring = inspect.cleandoc(docstring)
- data_doc = ("""\
- If given, all parameters also accept a string ``s``, which is
- interpreted as ``data[s]`` if ``s`` is a key in ``data``."""
- if replace_names is None else f"""\
- If given, the following parameters also accept a string ``s``, which is
- interpreted as ``data[s]`` if ``s`` is a key in ``data``:
- {', '.join(map('*{}*'.format, replace_names))}""")
- # using string replacement instead of formatting has the advantages
- # 1) simpler indent handling
- # 2) prevent problems with formatting characters '{', '%' in the docstring
- if _log.level <= logging.DEBUG:
- # test_data_parameter_replacement() tests against these log messages
- # make sure to keep message and test in sync
- if "data : indexable object, optional" not in docstring:
- _log.debug("data parameter docstring error: no data parameter")
- if 'DATA_PARAMETER_PLACEHOLDER' not in docstring:
- _log.debug("data parameter docstring error: missing placeholder")
- return docstring.replace(' DATA_PARAMETER_PLACEHOLDER', data_doc)
- def _preprocess_data(func=None, *, replace_names=None, label_namer=None):
- """
- A decorator to add a 'data' kwarg to a function.
- When applied::
- @_preprocess_data()
- def func(ax, *args, **kwargs): ...
- the signature is modified to ``decorated(ax, *args, data=None, **kwargs)``
- with the following behavior:
- - if called with ``data=None``, forward the other arguments to ``func``;
- - otherwise, *data* must be a mapping; for any argument passed in as a
- string ``name``, replace the argument by ``data[name]`` (if this does not
- throw an exception), then forward the arguments to ``func``.
- In either case, any argument that is a `MappingView` is also converted to a
- list.
- Parameters
- ----------
- replace_names : list of str or None, default: None
- The list of parameter names for which lookup into *data* should be
- attempted. If None, replacement is attempted for all arguments.
- label_namer : str, default: None
- If set e.g. to "namer" (which must be a kwarg in the function's
- signature -- not as ``**kwargs``), if the *namer* argument passed in is
- a (string) key of *data* and no *label* kwarg is passed, then use the
- (string) value of the *namer* as *label*. ::
- @_preprocess_data(label_namer="foo")
- def func(foo, label=None): ...
- func("key", data={"key": value})
- # is equivalent to
- func.__wrapped__(value, label="key")
- """
- if func is None: # Return the actual decorator.
- return functools.partial(
- _preprocess_data,
- replace_names=replace_names, label_namer=label_namer)
- sig = inspect.signature(func)
- varargs_name = None
- varkwargs_name = None
- arg_names = []
- params = list(sig.parameters.values())
- for p in params:
- if p.kind is Parameter.VAR_POSITIONAL:
- varargs_name = p.name
- elif p.kind is Parameter.VAR_KEYWORD:
- varkwargs_name = p.name
- else:
- arg_names.append(p.name)
- data_param = Parameter("data", Parameter.KEYWORD_ONLY, default=None)
- if varkwargs_name:
- params.insert(-1, data_param)
- else:
- params.append(data_param)
- new_sig = sig.replace(parameters=params)
- arg_names = arg_names[1:] # remove the first "ax" / self arg
- assert {*arg_names}.issuperset(replace_names or []) or varkwargs_name, (
- "Matplotlib internal error: invalid replace_names "
- f"({replace_names!r}) for {func.__name__!r}")
- assert label_namer is None or label_namer in arg_names, (
- "Matplotlib internal error: invalid label_namer "
- f"({label_namer!r}) for {func.__name__!r}")
- @functools.wraps(func)
- def inner(ax, *args, data=None, **kwargs):
- if data is None:
- return func(
- ax,
- *map(cbook.sanitize_sequence, args),
- **{k: cbook.sanitize_sequence(v) for k, v in kwargs.items()})
- bound = new_sig.bind(ax, *args, **kwargs)
- auto_label = (bound.arguments.get(label_namer)
- or bound.kwargs.get(label_namer))
- for k, v in bound.arguments.items():
- if k == varkwargs_name:
- for k1, v1 in v.items():
- if replace_names is None or k1 in replace_names:
- v[k1] = _replacer(data, v1)
- elif k == varargs_name:
- if replace_names is None:
- bound.arguments[k] = tuple(_replacer(data, v1) for v1 in v)
- else:
- if replace_names is None or k in replace_names:
- bound.arguments[k] = _replacer(data, v)
- new_args = bound.args
- new_kwargs = bound.kwargs
- args_and_kwargs = {**bound.arguments, **bound.kwargs}
- if label_namer and "label" not in args_and_kwargs:
- new_kwargs["label"] = _label_from_arg(
- args_and_kwargs.get(label_namer), auto_label)
- return func(*new_args, **new_kwargs)
- inner.__doc__ = _add_data_doc(inner.__doc__, replace_names)
- inner.__signature__ = new_sig
- return inner
- _log.debug('interactive is %s', is_interactive())
- _log.debug('platform is %s', sys.platform)
- @_api.deprecated("3.10", alternative="matplotlib.cbook.sanitize_sequence")
- def sanitize_sequence(data):
- return cbook.sanitize_sequence(data)
- @_api.deprecated("3.10", alternative="matplotlib.rcsetup.validate_backend")
- def validate_backend(s):
- return rcsetup.validate_backend(s)
- # workaround: we must defer colormaps import to after loading rcParams, because
- # colormap creation depends on rcParams
- from matplotlib.cm import _colormaps as colormaps # noqa: E402
- from matplotlib.cm import _multivar_colormaps as multivar_colormaps # noqa: E402
- from matplotlib.cm import _bivar_colormaps as bivar_colormaps # noqa: E402
- from matplotlib.colors import _color_sequences as color_sequences # noqa: E402
|