|
""" |
|
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", |
|
"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 |
|
import warnings |
|
|
|
import numpy |
|
from packaging.version import parse as parse_version |
|
|
|
|
|
|
|
from . import _api, _version, cbook, _docstring, rcsetup |
|
from matplotlib.cbook import sanitize_sequence |
|
from matplotlib._api import MatplotlibDeprecationWarning |
|
from matplotlib.rcsetup import validate_backend, cycler |
|
|
|
|
|
_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 |
|
}""" |
|
|
|
|
|
_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: |
|
|
|
return _VersionInfo(v.major, v.minor, v.micro + 1, 'alpha', v.post) |
|
|
|
|
|
def _get_version(): |
|
"""Return the version string used for __version__.""" |
|
|
|
|
|
|
|
root = Path(__file__).resolve().parents[2] |
|
if ((root / ".matplotlib-repo").exists() |
|
and (root / ".git").exists() |
|
and not (root / ".git/shallow").exists()): |
|
import setuptools_scm |
|
return setuptools_scm.get_version( |
|
root=root, |
|
version_scheme="release-branch-semver", |
|
local_scheme="node-and-date", |
|
fallback_version=_version.version, |
|
) |
|
else: |
|
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(): |
|
|
|
|
|
|
|
from . import ft2font |
|
|
|
for modname, minver in [ |
|
("cycler", "0.10"), |
|
("dateutil", "2.7"), |
|
("kiwisolver", "1.3.1"), |
|
("numpy", "1.21"), |
|
("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() |
|
|
|
|
|
|
|
|
|
@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 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): |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
output = subprocess.check_output( |
|
args, stderr=subprocess.STDOUT, |
|
text=True, errors="replace") |
|
except subprocess.CalledProcessError as _cpe: |
|
if ignore_exit_code: |
|
output = _cpe.output |
|
else: |
|
raise ExecutableNotFoundError(str(_cpe)) from _cpe |
|
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"] |
|
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: |
|
|
|
return impl(["inkscape", "--without-gui", "-V"], |
|
"Inkscape ([^ ]*)") |
|
except ExecutableNotFoundError: |
|
pass |
|
|
|
|
|
return impl(["inkscape", "-V"], "Inkscape ([^ ]*)") |
|
elif name == "magick": |
|
if sys.platform == "win32": |
|
|
|
|
|
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": |
|
|
|
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 |
|
|
|
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).resolve() |
|
elif sys.platform.startswith(('linux', 'freebsd')): |
|
|
|
|
|
configdir = Path(xdg_base_getter(), "matplotlib") |
|
else: |
|
configdir = Path.home() / ".matplotlib" |
|
try: |
|
configdir.mkdir(parents=True, exist_ok=True) |
|
except OSError: |
|
pass |
|
else: |
|
if os.access(str(configdir), os.W_OK) and configdir.is_dir(): |
|
return str(configdir) |
|
|
|
|
|
try: |
|
tmpdir = tempfile.mkdtemp(prefix="matplotlib-") |
|
except OSError as exc: |
|
raise OSError( |
|
f"Matplotlib requires access to a writable cache directory, but the " |
|
f"default path ({configdir}) is not a writable directory, 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 the default path " |
|
"(%s) is not a writable directory; 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(): |
|
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
|
_deprecated_map = {} |
|
|
|
|
|
_deprecated_ignore_map = {} |
|
|
|
|
|
|
|
_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 |
|
|
|
|
|
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 __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 |
|
|
|
|
|
|
|
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: |
|
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] |
|
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 |
|
else: |
|
try: |
|
config[key] = val |
|
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 = ('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"), |
|
|
|
transform=lambda line: line[1:] if line.startswith("#") else line, |
|
fail_on_error=True) |
|
dict.update(rcParamsDefault, rcsetup._hardcoded_defaults) |
|
|
|
|
|
|
|
|
|
|
|
dict.setdefault(rcParamsDefault, "backend", rcsetup._auto_backend_sentinel) |
|
rcParams = RcParams() |
|
dict.update(rcParams, dict.items(rcParamsDefault)) |
|
dict.update(rcParams, _rc_params_in_file(matplotlib_fname())) |
|
rcParamsOrig = rcParams.copy() |
|
with _api.suppress_matplotlib_deprecation_warning(): |
|
|
|
|
|
defaultParams = rcsetup.defaultParams = { |
|
|
|
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. |
|
""" |
|
|
|
|
|
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. |
|
""" |
|
|
|
|
|
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. |
|
""" |
|
|
|
|
|
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: |
|
dict.update(rcParams, orig) |
|
|
|
|
|
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, 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``. |
|
|
|
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 = validate_backend(backend) |
|
|
|
if rcParams._get_backend_or_none() == name: |
|
|
|
pass |
|
else: |
|
|
|
|
|
|
|
plt = sys.modules.get('matplotlib.pyplot') |
|
|
|
if plt is not None: |
|
try: |
|
|
|
|
|
|
|
plt.switch_backend(name) |
|
except ImportError: |
|
if force: |
|
raise |
|
|
|
|
|
|
|
else: |
|
rcParams['backend'] = backend |
|
|
|
|
|
rcParams['backend_fallback'] = False |
|
|
|
|
|
if os.environ.get('MPLBACKEND'): |
|
rcParams['backend'] = os.environ.get('MPLBACKEND') |
|
|
|
|
|
def get_backend(): |
|
""" |
|
Return the name of the current backend. |
|
|
|
See Also |
|
-------- |
|
matplotlib.use |
|
""" |
|
return rcParams['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(): |
|
|
|
|
|
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( |
|
f"Matplotlib is not built with the correct FreeType version to " |
|
f"run tests. Rebuild without setting system_freetype=1 in " |
|
f"mplsetup.cfg. Expect many image comparison failures below. " |
|
f"Expected freetype version {LOCAL_FREETYPE_VERSION}. " |
|
f"Found freetype version {ft2font.__freetype_version__}. " |
|
"Freetype build type is {}local".format( |
|
"" 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 isinstance(value, str): |
|
|
|
value = data[value] |
|
except Exception: |
|
|
|
pass |
|
return 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]`` (unless this raises an exception).""" |
|
if replace_names is None else f"""\ |
|
If given, the following parameters also accept a string ``s``, which is |
|
interpreted as ``data[s]`` (unless this raises an exception): |
|
|
|
{', '.join(map('*{}*'.format, replace_names))}""") |
|
|
|
|
|
|
|
if _log.level <= logging.DEBUG: |
|
|
|
|
|
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 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:] |
|
|
|
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(sanitize_sequence, args), **kwargs) |
|
|
|
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) |
|
|
|
|
|
|
|
|
|
from matplotlib.cm import _colormaps as colormaps |
|
from matplotlib.colors import _color_sequences as color_sequences |
|
|