up follow livre
This commit is contained in:
parent
b4b4398bb0
commit
3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions
1579
venv/lib/python3.13/site-packages/matplotlib/__init__.py
Normal file
1579
venv/lib/python3.13/site-packages/matplotlib/__init__.py
Normal file
File diff suppressed because it is too large
Load diff
124
venv/lib/python3.13/site-packages/matplotlib/__init__.pyi
Normal file
124
venv/lib/python3.13/site-packages/matplotlib/__init__.pyi
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
__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 os
|
||||
from pathlib import Path
|
||||
|
||||
from collections.abc import Callable, Generator
|
||||
import contextlib
|
||||
from packaging.version import Version
|
||||
|
||||
from matplotlib._api import MatplotlibDeprecationWarning
|
||||
from typing import Any, Literal, NamedTuple, overload
|
||||
|
||||
class _VersionInfo(NamedTuple):
|
||||
major: int
|
||||
minor: int
|
||||
micro: int
|
||||
releaselevel: str
|
||||
serial: int
|
||||
|
||||
__bibtex__: str
|
||||
__version__: str
|
||||
__version_info__: _VersionInfo
|
||||
|
||||
def set_loglevel(level: str) -> None: ...
|
||||
|
||||
class _ExecInfo(NamedTuple):
|
||||
executable: str
|
||||
raw_version: str
|
||||
version: Version
|
||||
|
||||
class ExecutableNotFoundError(FileNotFoundError): ...
|
||||
|
||||
def _get_executable_info(name: str) -> _ExecInfo: ...
|
||||
def get_configdir() -> str: ...
|
||||
def get_cachedir() -> str: ...
|
||||
def get_data_path() -> str: ...
|
||||
def matplotlib_fname() -> str: ...
|
||||
|
||||
class RcParams(dict[str, Any]):
|
||||
validate: dict[str, Callable]
|
||||
def __init__(self, *args, **kwargs) -> None: ...
|
||||
def _set(self, key: str, val: Any) -> None: ...
|
||||
def _get(self, key: str) -> Any: ...
|
||||
|
||||
def _update_raw(self, other_params: dict | RcParams) -> None: ...
|
||||
|
||||
def _ensure_has_backend(self) -> None: ...
|
||||
def __setitem__(self, key: str, val: Any) -> None: ...
|
||||
def __getitem__(self, key: str) -> Any: ...
|
||||
def __iter__(self) -> Generator[str, None, None]: ...
|
||||
def __len__(self) -> int: ...
|
||||
def find_all(self, pattern: str) -> RcParams: ...
|
||||
def copy(self) -> RcParams: ...
|
||||
|
||||
def rc_params(fail_on_error: bool = ...) -> RcParams: ...
|
||||
def rc_params_from_file(
|
||||
fname: str | Path | os.PathLike,
|
||||
fail_on_error: bool = ...,
|
||||
use_default_template: bool = ...,
|
||||
) -> RcParams: ...
|
||||
|
||||
rcParamsDefault: RcParams
|
||||
rcParams: RcParams
|
||||
rcParamsOrig: RcParams
|
||||
defaultParams: dict[str, Any]
|
||||
|
||||
def rc(group: str, **kwargs) -> None: ...
|
||||
def rcdefaults() -> None: ...
|
||||
def rc_file_defaults() -> None: ...
|
||||
def rc_file(
|
||||
fname: str | Path | os.PathLike, *, use_default_template: bool = ...
|
||||
) -> None: ...
|
||||
@contextlib.contextmanager
|
||||
def rc_context(
|
||||
rc: dict[str, Any] | None = ..., fname: str | Path | os.PathLike | None = ...
|
||||
) -> Generator[None, None, None]: ...
|
||||
def use(backend: str, *, force: bool = ...) -> None: ...
|
||||
@overload
|
||||
def get_backend(*, auto_select: Literal[True] = True) -> str: ...
|
||||
@overload
|
||||
def get_backend(*, auto_select: Literal[False]) -> str | None: ...
|
||||
def interactive(b: bool) -> None: ...
|
||||
def is_interactive() -> bool: ...
|
||||
|
||||
def _preprocess_data(
|
||||
func: Callable | None = ...,
|
||||
*,
|
||||
replace_names: list[str] | None = ...,
|
||||
label_namer: str | None = ...
|
||||
) -> Callable: ...
|
||||
|
||||
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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
532
venv/lib/python3.13/site-packages/matplotlib/_afm.py
Normal file
532
venv/lib/python3.13/site-packages/matplotlib/_afm.py
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
"""
|
||||
A python interface to Adobe Font Metrics Files.
|
||||
|
||||
Although a number of other Python implementations exist, and may be more
|
||||
complete than this, it was decided not to go with them because they were
|
||||
either:
|
||||
|
||||
1) copyrighted or used a non-BSD compatible license
|
||||
2) had too many dependencies and a free standing lib was needed
|
||||
3) did more than needed and it was easier to write afresh rather than
|
||||
figure out how to get just what was needed.
|
||||
|
||||
It is pretty easy to use, and has no external dependencies:
|
||||
|
||||
>>> import matplotlib as mpl
|
||||
>>> from pathlib import Path
|
||||
>>> afm_path = Path(mpl.get_data_path(), 'fonts', 'afm', 'ptmr8a.afm')
|
||||
>>>
|
||||
>>> from matplotlib.afm import AFM
|
||||
>>> with afm_path.open('rb') as fh:
|
||||
... afm = AFM(fh)
|
||||
>>> afm.string_width_height('What the heck?')
|
||||
(6220.0, 694)
|
||||
>>> afm.get_fontname()
|
||||
'Times-Roman'
|
||||
>>> afm.get_kern_dist('A', 'f')
|
||||
0
|
||||
>>> afm.get_kern_dist('A', 'y')
|
||||
-92.0
|
||||
>>> afm.get_bbox_char('!')
|
||||
[130, -9, 238, 676]
|
||||
|
||||
As in the Adobe Font Metrics File Format Specification, all dimensions
|
||||
are given in units of 1/1000 of the scale factor (point size) of the font
|
||||
being used.
|
||||
"""
|
||||
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import re
|
||||
|
||||
from ._mathtext_data import uni2type1
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _to_int(x):
|
||||
# Some AFM files have floats where we are expecting ints -- there is
|
||||
# probably a better way to handle this (support floats, round rather than
|
||||
# truncate). But I don't know what the best approach is now and this
|
||||
# change to _to_int should at least prevent Matplotlib from crashing on
|
||||
# these. JDH (2009-11-06)
|
||||
return int(float(x))
|
||||
|
||||
|
||||
def _to_float(x):
|
||||
# Some AFM files use "," instead of "." as decimal separator -- this
|
||||
# shouldn't be ambiguous (unless someone is wicked enough to use "," as
|
||||
# thousands separator...).
|
||||
if isinstance(x, bytes):
|
||||
# Encoding doesn't really matter -- if we have codepoints >127 the call
|
||||
# to float() will error anyways.
|
||||
x = x.decode('latin-1')
|
||||
return float(x.replace(',', '.'))
|
||||
|
||||
|
||||
def _to_str(x):
|
||||
return x.decode('utf8')
|
||||
|
||||
|
||||
def _to_list_of_ints(s):
|
||||
s = s.replace(b',', b' ')
|
||||
return [_to_int(val) for val in s.split()]
|
||||
|
||||
|
||||
def _to_list_of_floats(s):
|
||||
return [_to_float(val) for val in s.split()]
|
||||
|
||||
|
||||
def _to_bool(s):
|
||||
if s.lower().strip() in (b'false', b'0', b'no'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def _parse_header(fh):
|
||||
"""
|
||||
Read the font metrics header (up to the char metrics) and returns
|
||||
a dictionary mapping *key* to *val*. *val* will be converted to the
|
||||
appropriate python type as necessary; e.g.:
|
||||
|
||||
* 'False'->False
|
||||
* '0'->0
|
||||
* '-168 -218 1000 898'-> [-168, -218, 1000, 898]
|
||||
|
||||
Dictionary keys are
|
||||
|
||||
StartFontMetrics, FontName, FullName, FamilyName, Weight,
|
||||
ItalicAngle, IsFixedPitch, FontBBox, UnderlinePosition,
|
||||
UnderlineThickness, Version, Notice, EncodingScheme, CapHeight,
|
||||
XHeight, Ascender, Descender, StartCharMetrics
|
||||
"""
|
||||
header_converters = {
|
||||
b'StartFontMetrics': _to_float,
|
||||
b'FontName': _to_str,
|
||||
b'FullName': _to_str,
|
||||
b'FamilyName': _to_str,
|
||||
b'Weight': _to_str,
|
||||
b'ItalicAngle': _to_float,
|
||||
b'IsFixedPitch': _to_bool,
|
||||
b'FontBBox': _to_list_of_ints,
|
||||
b'UnderlinePosition': _to_float,
|
||||
b'UnderlineThickness': _to_float,
|
||||
b'Version': _to_str,
|
||||
# Some AFM files have non-ASCII characters (which are not allowed by
|
||||
# the spec). Given that there is actually no public API to even access
|
||||
# this field, just return it as straight bytes.
|
||||
b'Notice': lambda x: x,
|
||||
b'EncodingScheme': _to_str,
|
||||
b'CapHeight': _to_float, # Is the second version a mistake, or
|
||||
b'Capheight': _to_float, # do some AFM files contain 'Capheight'? -JKS
|
||||
b'XHeight': _to_float,
|
||||
b'Ascender': _to_float,
|
||||
b'Descender': _to_float,
|
||||
b'StdHW': _to_float,
|
||||
b'StdVW': _to_float,
|
||||
b'StartCharMetrics': _to_int,
|
||||
b'CharacterSet': _to_str,
|
||||
b'Characters': _to_int,
|
||||
}
|
||||
d = {}
|
||||
first_line = True
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if line.startswith(b'Comment'):
|
||||
continue
|
||||
lst = line.split(b' ', 1)
|
||||
key = lst[0]
|
||||
if first_line:
|
||||
# AFM spec, Section 4: The StartFontMetrics keyword
|
||||
# [followed by a version number] must be the first line in
|
||||
# the file, and the EndFontMetrics keyword must be the
|
||||
# last non-empty line in the file. We just check the
|
||||
# first header entry.
|
||||
if key != b'StartFontMetrics':
|
||||
raise RuntimeError('Not an AFM file')
|
||||
first_line = False
|
||||
if len(lst) == 2:
|
||||
val = lst[1]
|
||||
else:
|
||||
val = b''
|
||||
try:
|
||||
converter = header_converters[key]
|
||||
except KeyError:
|
||||
_log.error("Found an unknown keyword in AFM header (was %r)", key)
|
||||
continue
|
||||
try:
|
||||
d[key] = converter(val)
|
||||
except ValueError:
|
||||
_log.error('Value error parsing header in AFM: %s, %s', key, val)
|
||||
continue
|
||||
if key == b'StartCharMetrics':
|
||||
break
|
||||
else:
|
||||
raise RuntimeError('Bad parse')
|
||||
return d
|
||||
|
||||
|
||||
CharMetrics = namedtuple('CharMetrics', 'width, name, bbox')
|
||||
CharMetrics.__doc__ = """
|
||||
Represents the character metrics of a single character.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The fields do currently only describe a subset of character metrics
|
||||
information defined in the AFM standard.
|
||||
"""
|
||||
CharMetrics.width.__doc__ = """The character width (WX)."""
|
||||
CharMetrics.name.__doc__ = """The character name (N)."""
|
||||
CharMetrics.bbox.__doc__ = """
|
||||
The bbox of the character (B) as a tuple (*llx*, *lly*, *urx*, *ury*)."""
|
||||
|
||||
|
||||
def _parse_char_metrics(fh):
|
||||
"""
|
||||
Parse the given filehandle for character metrics information and return
|
||||
the information as dicts.
|
||||
|
||||
It is assumed that the file cursor is on the line behind
|
||||
'StartCharMetrics'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
ascii_d : dict
|
||||
A mapping "ASCII num of the character" to `.CharMetrics`.
|
||||
name_d : dict
|
||||
A mapping "character name" to `.CharMetrics`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function is incomplete per the standard, but thus far parses
|
||||
all the sample afm files tried.
|
||||
"""
|
||||
required_keys = {'C', 'WX', 'N', 'B'}
|
||||
|
||||
ascii_d = {}
|
||||
name_d = {}
|
||||
for line in fh:
|
||||
# We are defensively letting values be utf8. The spec requires
|
||||
# ascii, but there are non-compliant fonts in circulation
|
||||
line = _to_str(line.rstrip()) # Convert from byte-literal
|
||||
if line.startswith('EndCharMetrics'):
|
||||
return ascii_d, name_d
|
||||
# Split the metric line into a dictionary, keyed by metric identifiers
|
||||
vals = dict(s.strip().split(' ', 1) for s in line.split(';') if s)
|
||||
# There may be other metrics present, but only these are needed
|
||||
if not required_keys.issubset(vals):
|
||||
raise RuntimeError('Bad char metrics line: %s' % line)
|
||||
num = _to_int(vals['C'])
|
||||
wx = _to_float(vals['WX'])
|
||||
name = vals['N']
|
||||
bbox = _to_list_of_floats(vals['B'])
|
||||
bbox = list(map(int, bbox))
|
||||
metrics = CharMetrics(wx, name, bbox)
|
||||
# Workaround: If the character name is 'Euro', give it the
|
||||
# corresponding character code, according to WinAnsiEncoding (see PDF
|
||||
# Reference).
|
||||
if name == 'Euro':
|
||||
num = 128
|
||||
elif name == 'minus':
|
||||
num = ord("\N{MINUS SIGN}") # 0x2212
|
||||
if num != -1:
|
||||
ascii_d[num] = metrics
|
||||
name_d[name] = metrics
|
||||
raise RuntimeError('Bad parse')
|
||||
|
||||
|
||||
def _parse_kern_pairs(fh):
|
||||
"""
|
||||
Return a kern pairs dictionary; keys are (*char1*, *char2*) tuples and
|
||||
values are the kern pair value. For example, a kern pairs line like
|
||||
``KPX A y -50``
|
||||
|
||||
will be represented as::
|
||||
|
||||
d[ ('A', 'y') ] = -50
|
||||
|
||||
"""
|
||||
|
||||
line = next(fh)
|
||||
if not line.startswith(b'StartKernPairs'):
|
||||
raise RuntimeError('Bad start of kern pairs data: %s' % line)
|
||||
|
||||
d = {}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith(b'EndKernPairs'):
|
||||
next(fh) # EndKernData
|
||||
return d
|
||||
vals = line.split()
|
||||
if len(vals) != 4 or vals[0] != b'KPX':
|
||||
raise RuntimeError('Bad kern pairs line: %s' % line)
|
||||
c1, c2, val = _to_str(vals[1]), _to_str(vals[2]), _to_float(vals[3])
|
||||
d[(c1, c2)] = val
|
||||
raise RuntimeError('Bad kern pairs parse')
|
||||
|
||||
|
||||
CompositePart = namedtuple('CompositePart', 'name, dx, dy')
|
||||
CompositePart.__doc__ = """
|
||||
Represents the information on a composite element of a composite char."""
|
||||
CompositePart.name.__doc__ = """Name of the part, e.g. 'acute'."""
|
||||
CompositePart.dx.__doc__ = """x-displacement of the part from the origin."""
|
||||
CompositePart.dy.__doc__ = """y-displacement of the part from the origin."""
|
||||
|
||||
|
||||
def _parse_composites(fh):
|
||||
"""
|
||||
Parse the given filehandle for composites information return them as a
|
||||
dict.
|
||||
|
||||
It is assumed that the file cursor is on the line behind 'StartComposites'.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
A dict mapping composite character names to a parts list. The parts
|
||||
list is a list of `.CompositePart` entries describing the parts of
|
||||
the composite.
|
||||
|
||||
Examples
|
||||
--------
|
||||
A composite definition line::
|
||||
|
||||
CC Aacute 2 ; PCC A 0 0 ; PCC acute 160 170 ;
|
||||
|
||||
will be represented as::
|
||||
|
||||
composites['Aacute'] = [CompositePart(name='A', dx=0, dy=0),
|
||||
CompositePart(name='acute', dx=160, dy=170)]
|
||||
|
||||
"""
|
||||
composites = {}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
if line.startswith(b'EndComposites'):
|
||||
return composites
|
||||
vals = line.split(b';')
|
||||
cc = vals[0].split()
|
||||
name, _num_parts = cc[1], _to_int(cc[2])
|
||||
pccParts = []
|
||||
for s in vals[1:-1]:
|
||||
pcc = s.split()
|
||||
part = CompositePart(pcc[1], _to_float(pcc[2]), _to_float(pcc[3]))
|
||||
pccParts.append(part)
|
||||
composites[name] = pccParts
|
||||
|
||||
raise RuntimeError('Bad composites parse')
|
||||
|
||||
|
||||
def _parse_optional(fh):
|
||||
"""
|
||||
Parse the optional fields for kern pair data and composites.
|
||||
|
||||
Returns
|
||||
-------
|
||||
kern_data : dict
|
||||
A dict containing kerning information. May be empty.
|
||||
See `._parse_kern_pairs`.
|
||||
composites : dict
|
||||
A dict containing composite information. May be empty.
|
||||
See `._parse_composites`.
|
||||
"""
|
||||
optional = {
|
||||
b'StartKernData': _parse_kern_pairs,
|
||||
b'StartComposites': _parse_composites,
|
||||
}
|
||||
|
||||
d = {b'StartKernData': {},
|
||||
b'StartComposites': {}}
|
||||
for line in fh:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
key = line.split()[0]
|
||||
|
||||
if key in optional:
|
||||
d[key] = optional[key](fh)
|
||||
|
||||
return d[b'StartKernData'], d[b'StartComposites']
|
||||
|
||||
|
||||
class AFM:
|
||||
|
||||
def __init__(self, fh):
|
||||
"""Parse the AFM file in file object *fh*."""
|
||||
self._header = _parse_header(fh)
|
||||
self._metrics, self._metrics_by_name = _parse_char_metrics(fh)
|
||||
self._kern, self._composite = _parse_optional(fh)
|
||||
|
||||
def get_bbox_char(self, c, isord=False):
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].bbox
|
||||
|
||||
def string_width_height(self, s):
|
||||
"""
|
||||
Return the string width (including kerning) and string height
|
||||
as a (*w*, *h*) tuple.
|
||||
"""
|
||||
if not len(s):
|
||||
return 0, 0
|
||||
total_width = 0
|
||||
namelast = None
|
||||
miny = 1e9
|
||||
maxy = 0
|
||||
for c in s:
|
||||
if c == '\n':
|
||||
continue
|
||||
wx, name, bbox = self._metrics[ord(c)]
|
||||
|
||||
total_width += wx + self._kern.get((namelast, name), 0)
|
||||
l, b, w, h = bbox
|
||||
miny = min(miny, b)
|
||||
maxy = max(maxy, b + h)
|
||||
|
||||
namelast = name
|
||||
|
||||
return total_width, maxy - miny
|
||||
|
||||
def get_str_bbox_and_descent(self, s):
|
||||
"""Return the string bounding box and the maximal descent."""
|
||||
if not len(s):
|
||||
return 0, 0, 0, 0, 0
|
||||
total_width = 0
|
||||
namelast = None
|
||||
miny = 1e9
|
||||
maxy = 0
|
||||
left = 0
|
||||
if not isinstance(s, str):
|
||||
s = _to_str(s)
|
||||
for c in s:
|
||||
if c == '\n':
|
||||
continue
|
||||
name = uni2type1.get(ord(c), f"uni{ord(c):04X}")
|
||||
try:
|
||||
wx, _, bbox = self._metrics_by_name[name]
|
||||
except KeyError:
|
||||
name = 'question'
|
||||
wx, _, bbox = self._metrics_by_name[name]
|
||||
total_width += wx + self._kern.get((namelast, name), 0)
|
||||
l, b, w, h = bbox
|
||||
left = min(left, l)
|
||||
miny = min(miny, b)
|
||||
maxy = max(maxy, b + h)
|
||||
|
||||
namelast = name
|
||||
|
||||
return left, miny, total_width, maxy - miny, -miny
|
||||
|
||||
def get_str_bbox(self, s):
|
||||
"""Return the string bounding box."""
|
||||
return self.get_str_bbox_and_descent(s)[:4]
|
||||
|
||||
def get_name_char(self, c, isord=False):
|
||||
"""Get the name of the character, i.e., ';' is 'semicolon'."""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].name
|
||||
|
||||
def get_width_char(self, c, isord=False):
|
||||
"""
|
||||
Get the width of the character from the character metric WX field.
|
||||
"""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].width
|
||||
|
||||
def get_width_from_char_name(self, name):
|
||||
"""Get the width of the character from a type1 character name."""
|
||||
return self._metrics_by_name[name].width
|
||||
|
||||
def get_height_char(self, c, isord=False):
|
||||
"""Get the bounding box (ink) height of character *c* (space is 0)."""
|
||||
if not isord:
|
||||
c = ord(c)
|
||||
return self._metrics[c].bbox[-1]
|
||||
|
||||
def get_kern_dist(self, c1, c2):
|
||||
"""
|
||||
Return the kerning pair distance (possibly 0) for chars *c1* and *c2*.
|
||||
"""
|
||||
name1, name2 = self.get_name_char(c1), self.get_name_char(c2)
|
||||
return self.get_kern_dist_from_name(name1, name2)
|
||||
|
||||
def get_kern_dist_from_name(self, name1, name2):
|
||||
"""
|
||||
Return the kerning pair distance (possibly 0) for chars
|
||||
*name1* and *name2*.
|
||||
"""
|
||||
return self._kern.get((name1, name2), 0)
|
||||
|
||||
def get_fontname(self):
|
||||
"""Return the font name, e.g., 'Times-Roman'."""
|
||||
return self._header[b'FontName']
|
||||
|
||||
@property
|
||||
def postscript_name(self): # For consistency with FT2Font.
|
||||
return self.get_fontname()
|
||||
|
||||
def get_fullname(self):
|
||||
"""Return the font full name, e.g., 'Times-Roman'."""
|
||||
name = self._header.get(b'FullName')
|
||||
if name is None: # use FontName as a substitute
|
||||
name = self._header[b'FontName']
|
||||
return name
|
||||
|
||||
def get_familyname(self):
|
||||
"""Return the font family name, e.g., 'Times'."""
|
||||
name = self._header.get(b'FamilyName')
|
||||
if name is not None:
|
||||
return name
|
||||
|
||||
# FamilyName not specified so we'll make a guess
|
||||
name = self.get_fullname()
|
||||
extras = (r'(?i)([ -](regular|plain|italic|oblique|bold|semibold|'
|
||||
r'light|ultralight|extra|condensed))+$')
|
||||
return re.sub(extras, '', name)
|
||||
|
||||
@property
|
||||
def family_name(self):
|
||||
"""The font family name, e.g., 'Times'."""
|
||||
return self.get_familyname()
|
||||
|
||||
def get_weight(self):
|
||||
"""Return the font weight, e.g., 'Bold' or 'Roman'."""
|
||||
return self._header[b'Weight']
|
||||
|
||||
def get_angle(self):
|
||||
"""Return the fontangle as float."""
|
||||
return self._header[b'ItalicAngle']
|
||||
|
||||
def get_capheight(self):
|
||||
"""Return the cap height as float."""
|
||||
return self._header[b'CapHeight']
|
||||
|
||||
def get_xheight(self):
|
||||
"""Return the xheight as float."""
|
||||
return self._header[b'XHeight']
|
||||
|
||||
def get_underline_thickness(self):
|
||||
"""Return the underline thickness as float."""
|
||||
return self._header[b'UnderlineThickness']
|
||||
|
||||
def get_horizontal_stem_width(self):
|
||||
"""
|
||||
Return the standard horizontal stem width as float, or *None* if
|
||||
not specified in AFM file.
|
||||
"""
|
||||
return self._header.get(b'StdHW', None)
|
||||
|
||||
def get_vertical_stem_width(self):
|
||||
"""
|
||||
Return the standard vertical stem width as float, or *None* if
|
||||
not specified in AFM file.
|
||||
"""
|
||||
return self._header.get(b'StdVW', None)
|
||||
262
venv/lib/python3.13/site-packages/matplotlib/_animation_data.py
Normal file
262
venv/lib/python3.13/site-packages/matplotlib/_animation_data.py
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
# JavaScript template for HTMLWriter
|
||||
JS_INCLUDE = """
|
||||
<link rel="stylesheet"
|
||||
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
|
||||
<script language="javascript">
|
||||
function isInternetExplorer() {
|
||||
ua = navigator.userAgent;
|
||||
/* MSIE used to detect old browsers and Trident used to newer ones*/
|
||||
return ua.indexOf("MSIE ") > -1 || ua.indexOf("Trident/") > -1;
|
||||
}
|
||||
|
||||
/* Define the Animation class */
|
||||
function Animation(frames, img_id, slider_id, interval, loop_select_id){
|
||||
this.img_id = img_id;
|
||||
this.slider_id = slider_id;
|
||||
this.loop_select_id = loop_select_id;
|
||||
this.interval = interval;
|
||||
this.current_frame = 0;
|
||||
this.direction = 0;
|
||||
this.timer = null;
|
||||
this.frames = new Array(frames.length);
|
||||
|
||||
for (var i=0; i<frames.length; i++)
|
||||
{
|
||||
this.frames[i] = new Image();
|
||||
this.frames[i].src = frames[i];
|
||||
}
|
||||
var slider = document.getElementById(this.slider_id);
|
||||
slider.max = this.frames.length - 1;
|
||||
if (isInternetExplorer()) {
|
||||
// switch from oninput to onchange because IE <= 11 does not conform
|
||||
// with W3C specification. It ignores oninput and onchange behaves
|
||||
// like oninput. In contrast, Microsoft Edge behaves correctly.
|
||||
slider.setAttribute('onchange', slider.getAttribute('oninput'));
|
||||
slider.setAttribute('oninput', null);
|
||||
}
|
||||
this.set_frame(this.current_frame);
|
||||
}
|
||||
|
||||
Animation.prototype.get_loop_state = function(){
|
||||
var button_group = document[this.loop_select_id].state;
|
||||
for (var i = 0; i < button_group.length; i++) {
|
||||
var button = button_group[i];
|
||||
if (button.checked) {
|
||||
return button.value;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
Animation.prototype.set_frame = function(frame){
|
||||
this.current_frame = frame;
|
||||
document.getElementById(this.img_id).src =
|
||||
this.frames[this.current_frame].src;
|
||||
document.getElementById(this.slider_id).value = this.current_frame;
|
||||
}
|
||||
|
||||
Animation.prototype.next_frame = function()
|
||||
{
|
||||
this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));
|
||||
}
|
||||
|
||||
Animation.prototype.previous_frame = function()
|
||||
{
|
||||
this.set_frame(Math.max(0, this.current_frame - 1));
|
||||
}
|
||||
|
||||
Animation.prototype.first_frame = function()
|
||||
{
|
||||
this.set_frame(0);
|
||||
}
|
||||
|
||||
Animation.prototype.last_frame = function()
|
||||
{
|
||||
this.set_frame(this.frames.length - 1);
|
||||
}
|
||||
|
||||
Animation.prototype.slower = function()
|
||||
{
|
||||
this.interval /= 0.7;
|
||||
if(this.direction > 0){this.play_animation();}
|
||||
else if(this.direction < 0){this.reverse_animation();}
|
||||
}
|
||||
|
||||
Animation.prototype.faster = function()
|
||||
{
|
||||
this.interval *= 0.7;
|
||||
if(this.direction > 0){this.play_animation();}
|
||||
else if(this.direction < 0){this.reverse_animation();}
|
||||
}
|
||||
|
||||
Animation.prototype.anim_step_forward = function()
|
||||
{
|
||||
this.current_frame += 1;
|
||||
if(this.current_frame < this.frames.length){
|
||||
this.set_frame(this.current_frame);
|
||||
}else{
|
||||
var loop_state = this.get_loop_state();
|
||||
if(loop_state == "loop"){
|
||||
this.first_frame();
|
||||
}else if(loop_state == "reflect"){
|
||||
this.last_frame();
|
||||
this.reverse_animation();
|
||||
}else{
|
||||
this.pause_animation();
|
||||
this.last_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.anim_step_reverse = function()
|
||||
{
|
||||
this.current_frame -= 1;
|
||||
if(this.current_frame >= 0){
|
||||
this.set_frame(this.current_frame);
|
||||
}else{
|
||||
var loop_state = this.get_loop_state();
|
||||
if(loop_state == "loop"){
|
||||
this.last_frame();
|
||||
}else if(loop_state == "reflect"){
|
||||
this.first_frame();
|
||||
this.play_animation();
|
||||
}else{
|
||||
this.pause_animation();
|
||||
this.first_frame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.pause_animation = function()
|
||||
{
|
||||
this.direction = 0;
|
||||
if (this.timer){
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
Animation.prototype.play_animation = function()
|
||||
{
|
||||
this.pause_animation();
|
||||
this.direction = 1;
|
||||
var t = this;
|
||||
if (!this.timer) this.timer = setInterval(function() {
|
||||
t.anim_step_forward();
|
||||
}, this.interval);
|
||||
}
|
||||
|
||||
Animation.prototype.reverse_animation = function()
|
||||
{
|
||||
this.pause_animation();
|
||||
this.direction = -1;
|
||||
var t = this;
|
||||
if (!this.timer) this.timer = setInterval(function() {
|
||||
t.anim_step_reverse();
|
||||
}, this.interval);
|
||||
}
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
# Style definitions for the HTML template
|
||||
STYLE_INCLUDE = """
|
||||
<style>
|
||||
.animation {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
}
|
||||
input[type=range].anim-slider {
|
||||
width: 374px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.anim-buttons {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
.anim-buttons button {
|
||||
padding: 0;
|
||||
width: 36px;
|
||||
}
|
||||
.anim-state label {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.anim-state input {
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
|
||||
# HTML template for HTMLWriter
|
||||
DISPLAY_TEMPLATE = """
|
||||
<div class="animation">
|
||||
<img id="_anim_img{id}">
|
||||
<div class="anim-controls">
|
||||
<input id="_anim_slider{id}" type="range" class="anim-slider"
|
||||
name="points" min="0" max="1" step="1" value="0"
|
||||
oninput="anim{id}.set_frame(parseInt(this.value));">
|
||||
<div class="anim-buttons">
|
||||
<button title="Decrease speed" aria-label="Decrease speed" onclick="anim{id}.slower()">
|
||||
<i class="fa fa-minus"></i></button>
|
||||
<button title="First frame" aria-label="First frame" onclick="anim{id}.first_frame()">
|
||||
<i class="fa fa-fast-backward"></i></button>
|
||||
<button title="Previous frame" aria-label="Previous frame" onclick="anim{id}.previous_frame()">
|
||||
<i class="fa fa-step-backward"></i></button>
|
||||
<button title="Play backwards" aria-label="Play backwards" onclick="anim{id}.reverse_animation()">
|
||||
<i class="fa fa-play fa-flip-horizontal"></i></button>
|
||||
<button title="Pause" aria-label="Pause" onclick="anim{id}.pause_animation()">
|
||||
<i class="fa fa-pause"></i></button>
|
||||
<button title="Play" aria-label="Play" onclick="anim{id}.play_animation()">
|
||||
<i class="fa fa-play"></i></button>
|
||||
<button title="Next frame" aria-label="Next frame" onclick="anim{id}.next_frame()">
|
||||
<i class="fa fa-step-forward"></i></button>
|
||||
<button title="Last frame" aria-label="Last frame" onclick="anim{id}.last_frame()">
|
||||
<i class="fa fa-fast-forward"></i></button>
|
||||
<button title="Increase speed" aria-label="Increase speed" onclick="anim{id}.faster()">
|
||||
<i class="fa fa-plus"></i></button>
|
||||
</div>
|
||||
<form title="Repetition mode" aria-label="Repetition mode" action="#n" name="_anim_loop_select{id}"
|
||||
class="anim-state">
|
||||
<input type="radio" name="state" value="once" id="_anim_radio1_{id}"
|
||||
{once_checked}>
|
||||
<label for="_anim_radio1_{id}">Once</label>
|
||||
<input type="radio" name="state" value="loop" id="_anim_radio2_{id}"
|
||||
{loop_checked}>
|
||||
<label for="_anim_radio2_{id}">Loop</label>
|
||||
<input type="radio" name="state" value="reflect" id="_anim_radio3_{id}"
|
||||
{reflect_checked}>
|
||||
<label for="_anim_radio3_{id}">Reflect</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script language="javascript">
|
||||
/* Instantiate the Animation class. */
|
||||
/* The IDs given should match those used in the template above. */
|
||||
(function() {{
|
||||
var img_id = "_anim_img{id}";
|
||||
var slider_id = "_anim_slider{id}";
|
||||
var loop_select_id = "_anim_loop_select{id}";
|
||||
var frames = new Array({Nframes});
|
||||
{fill_frames}
|
||||
|
||||
/* set a timeout to make sure all the above elements are created before
|
||||
the object is initialized. */
|
||||
setTimeout(function() {{
|
||||
anim{id} = new Animation(frames, img_id, slider_id, {interval},
|
||||
loop_select_id);
|
||||
}}, 0);
|
||||
}})()
|
||||
</script>
|
||||
""" # noqa: E501
|
||||
|
||||
|
||||
INCLUDED_FRAMES = """
|
||||
for (var i=0; i<{Nframes}; i++){{
|
||||
frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) +
|
||||
".{frame_format}";
|
||||
}}
|
||||
"""
|
||||
391
venv/lib/python3.13/site-packages/matplotlib/_api/__init__.py
Normal file
391
venv/lib/python3.13/site-packages/matplotlib/_api/__init__.py
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
"""
|
||||
Helper functions for managing the Matplotlib API.
|
||||
|
||||
This documentation is only relevant for Matplotlib developers, not for users.
|
||||
|
||||
.. warning::
|
||||
|
||||
This module and its submodules are for internal use only. Do not use them
|
||||
in your own code. We may change the API at any time with no warning.
|
||||
|
||||
"""
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from .deprecation import ( # noqa: F401
|
||||
deprecated, warn_deprecated,
|
||||
rename_parameter, delete_parameter, make_keyword_only,
|
||||
deprecate_method_override, deprecate_privatize_attribute,
|
||||
suppress_matplotlib_deprecation_warning,
|
||||
MatplotlibDeprecationWarning)
|
||||
|
||||
|
||||
class classproperty:
|
||||
"""
|
||||
Like `property`, but also triggers on access via the class, and it is the
|
||||
*class* that's passed as argument.
|
||||
|
||||
Examples
|
||||
--------
|
||||
::
|
||||
|
||||
class C:
|
||||
@classproperty
|
||||
def foo(cls):
|
||||
return cls.__name__
|
||||
|
||||
assert C.foo == "C"
|
||||
"""
|
||||
|
||||
def __init__(self, fget, fset=None, fdel=None, doc=None):
|
||||
self._fget = fget
|
||||
if fset is not None or fdel is not None:
|
||||
raise ValueError('classproperty only implements fget.')
|
||||
self.fset = fset
|
||||
self.fdel = fdel
|
||||
# docs are ignored for now
|
||||
self._doc = doc
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
return self._fget(owner)
|
||||
|
||||
@property
|
||||
def fget(self):
|
||||
return self._fget
|
||||
|
||||
|
||||
# In the following check_foo() functions, the first parameter is positional-only to make
|
||||
# e.g. `_api.check_isinstance([...], types=foo)` work.
|
||||
|
||||
def check_isinstance(types, /, **kwargs):
|
||||
"""
|
||||
For each *key, value* pair in *kwargs*, check that *value* is an instance
|
||||
of one of *types*; if not, raise an appropriate TypeError.
|
||||
|
||||
As a special case, a ``None`` entry in *types* is treated as NoneType.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> _api.check_isinstance((SomeClass, None), arg=arg)
|
||||
"""
|
||||
none_type = type(None)
|
||||
types = ((types,) if isinstance(types, type) else
|
||||
(none_type,) if types is None else
|
||||
tuple(none_type if tp is None else tp for tp in types))
|
||||
|
||||
def type_name(tp):
|
||||
return ("None" if tp is none_type
|
||||
else tp.__qualname__ if tp.__module__ == "builtins"
|
||||
else f"{tp.__module__}.{tp.__qualname__}")
|
||||
|
||||
for k, v in kwargs.items():
|
||||
if not isinstance(v, types):
|
||||
names = [*map(type_name, types)]
|
||||
if "None" in names: # Move it to the end for better wording.
|
||||
names.remove("None")
|
||||
names.append("None")
|
||||
raise TypeError(
|
||||
"{!r} must be an instance of {}, not a {}".format(
|
||||
k,
|
||||
", ".join(names[:-1]) + " or " + names[-1]
|
||||
if len(names) > 1 else names[0],
|
||||
type_name(type(v))))
|
||||
|
||||
|
||||
def check_in_list(values, /, *, _print_supported_values=True, **kwargs):
|
||||
"""
|
||||
For each *key, value* pair in *kwargs*, check that *value* is in *values*;
|
||||
if not, raise an appropriate ValueError.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
values : iterable
|
||||
Sequence of values to check on.
|
||||
_print_supported_values : bool, default: True
|
||||
Whether to print *values* when raising ValueError.
|
||||
**kwargs : dict
|
||||
*key, value* pairs as keyword arguments to find in *values*.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If any *value* in *kwargs* is not found in *values*.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> _api.check_in_list(["foo", "bar"], arg=arg, other_arg=other_arg)
|
||||
"""
|
||||
if not kwargs:
|
||||
raise TypeError("No argument to check!")
|
||||
for key, val in kwargs.items():
|
||||
if val not in values:
|
||||
msg = f"{val!r} is not a valid value for {key}"
|
||||
if _print_supported_values:
|
||||
msg += f"; supported values are {', '.join(map(repr, values))}"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
def check_shape(shape, /, **kwargs):
|
||||
"""
|
||||
For each *key, value* pair in *kwargs*, check that *value* has the shape *shape*;
|
||||
if not, raise an appropriate ValueError.
|
||||
|
||||
*None* in the shape is treated as a "free" size that can have any length.
|
||||
e.g. (None, 2) -> (N, 2)
|
||||
|
||||
The values checked must be numpy arrays.
|
||||
|
||||
Examples
|
||||
--------
|
||||
To check for (N, 2) shaped arrays
|
||||
|
||||
>>> _api.check_shape((None, 2), arg=arg, other_arg=other_arg)
|
||||
"""
|
||||
for k, v in kwargs.items():
|
||||
data_shape = v.shape
|
||||
|
||||
if (len(data_shape) != len(shape)
|
||||
or any(s != t and t is not None for s, t in zip(data_shape, shape))):
|
||||
dim_labels = iter(itertools.chain(
|
||||
'NMLKJIH',
|
||||
(f"D{i}" for i in itertools.count())))
|
||||
text_shape = ", ".join([str(n) if n is not None else next(dim_labels)
|
||||
for n in shape[::-1]][::-1])
|
||||
if len(shape) == 1:
|
||||
text_shape += ","
|
||||
|
||||
raise ValueError(
|
||||
f"{k!r} must be {len(shape)}D with shape ({text_shape}), "
|
||||
f"but your input has shape {v.shape}"
|
||||
)
|
||||
|
||||
|
||||
def check_getitem(mapping, /, **kwargs):
|
||||
"""
|
||||
*kwargs* must consist of a single *key, value* pair. If *key* is in
|
||||
*mapping*, return ``mapping[value]``; else, raise an appropriate
|
||||
ValueError.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> _api.check_getitem({"foo": "bar"}, arg=arg)
|
||||
"""
|
||||
if len(kwargs) != 1:
|
||||
raise ValueError("check_getitem takes a single keyword argument")
|
||||
(k, v), = kwargs.items()
|
||||
try:
|
||||
return mapping[v]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
f"{v!r} is not a valid value for {k}; supported values are "
|
||||
f"{', '.join(map(repr, mapping))}") from None
|
||||
|
||||
|
||||
def caching_module_getattr(cls):
|
||||
"""
|
||||
Helper decorator for implementing module-level ``__getattr__`` as a class.
|
||||
|
||||
This decorator must be used at the module toplevel as follows::
|
||||
|
||||
@caching_module_getattr
|
||||
class __getattr__: # The class *must* be named ``__getattr__``.
|
||||
@property # Only properties are taken into account.
|
||||
def name(self): ...
|
||||
|
||||
The ``__getattr__`` class will be replaced by a ``__getattr__``
|
||||
function such that trying to access ``name`` on the module will
|
||||
resolve the corresponding property (which may be decorated e.g. with
|
||||
``_api.deprecated`` for deprecating module globals). The properties are
|
||||
all implicitly cached. Moreover, a suitable AttributeError is generated
|
||||
and raised if no property with the given name exists.
|
||||
"""
|
||||
|
||||
assert cls.__name__ == "__getattr__"
|
||||
# Don't accidentally export cls dunders.
|
||||
props = {name: prop for name, prop in vars(cls).items()
|
||||
if isinstance(prop, property)}
|
||||
instance = cls()
|
||||
|
||||
@functools.cache
|
||||
def __getattr__(name):
|
||||
if name in props:
|
||||
return props[name].__get__(instance)
|
||||
raise AttributeError(
|
||||
f"module {cls.__module__!r} has no attribute {name!r}")
|
||||
|
||||
return __getattr__
|
||||
|
||||
|
||||
def define_aliases(alias_d, cls=None):
|
||||
"""
|
||||
Class decorator for defining property aliases.
|
||||
|
||||
Use as ::
|
||||
|
||||
@_api.define_aliases({"property": ["alias", ...], ...})
|
||||
class C: ...
|
||||
|
||||
For each property, if the corresponding ``get_property`` is defined in the
|
||||
class so far, an alias named ``get_alias`` will be defined; the same will
|
||||
be done for setters. If neither the getter nor the setter exists, an
|
||||
exception will be raised.
|
||||
|
||||
The alias map is stored as the ``_alias_map`` attribute on the class and
|
||||
can be used by `.normalize_kwargs` (which assumes that higher priority
|
||||
aliases come last).
|
||||
"""
|
||||
if cls is None: # Return the actual class decorator.
|
||||
return functools.partial(define_aliases, alias_d)
|
||||
|
||||
def make_alias(name): # Enforce a closure over *name*.
|
||||
@functools.wraps(getattr(cls, name))
|
||||
def method(self, *args, **kwargs):
|
||||
return getattr(self, name)(*args, **kwargs)
|
||||
return method
|
||||
|
||||
for prop, aliases in alias_d.items():
|
||||
exists = False
|
||||
for prefix in ["get_", "set_"]:
|
||||
if prefix + prop in vars(cls):
|
||||
exists = True
|
||||
for alias in aliases:
|
||||
method = make_alias(prefix + prop)
|
||||
method.__name__ = prefix + alias
|
||||
method.__doc__ = f"Alias for `{prefix + prop}`."
|
||||
setattr(cls, prefix + alias, method)
|
||||
if not exists:
|
||||
raise ValueError(
|
||||
f"Neither getter nor setter exists for {prop!r}")
|
||||
|
||||
def get_aliased_and_aliases(d):
|
||||
return {*d, *(alias for aliases in d.values() for alias in aliases)}
|
||||
|
||||
preexisting_aliases = getattr(cls, "_alias_map", {})
|
||||
conflicting = (get_aliased_and_aliases(preexisting_aliases)
|
||||
& get_aliased_and_aliases(alias_d))
|
||||
if conflicting:
|
||||
# Need to decide on conflict resolution policy.
|
||||
raise NotImplementedError(
|
||||
f"Parent class already defines conflicting aliases: {conflicting}")
|
||||
cls._alias_map = {**preexisting_aliases, **alias_d}
|
||||
return cls
|
||||
|
||||
|
||||
def select_matching_signature(funcs, *args, **kwargs):
|
||||
"""
|
||||
Select and call the function that accepts ``*args, **kwargs``.
|
||||
|
||||
*funcs* is a list of functions which should not raise any exception (other
|
||||
than `TypeError` if the arguments passed do not match their signature).
|
||||
|
||||
`select_matching_signature` tries to call each of the functions in *funcs*
|
||||
with ``*args, **kwargs`` (in the order in which they are given). Calls
|
||||
that fail with a `TypeError` are silently skipped. As soon as a call
|
||||
succeeds, `select_matching_signature` returns its return value. If no
|
||||
function accepts ``*args, **kwargs``, then the `TypeError` raised by the
|
||||
last failing call is re-raised.
|
||||
|
||||
Callers should normally make sure that any ``*args, **kwargs`` can only
|
||||
bind a single *func* (to avoid any ambiguity), although this is not checked
|
||||
by `select_matching_signature`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
`select_matching_signature` is intended to help implementing
|
||||
signature-overloaded functions. In general, such functions should be
|
||||
avoided, except for back-compatibility concerns. A typical use pattern is
|
||||
::
|
||||
|
||||
def my_func(*args, **kwargs):
|
||||
params = select_matching_signature(
|
||||
[lambda old1, old2: locals(), lambda new: locals()],
|
||||
*args, **kwargs)
|
||||
if "old1" in params:
|
||||
warn_deprecated(...)
|
||||
old1, old2 = params.values() # note that locals() is ordered.
|
||||
else:
|
||||
new, = params.values()
|
||||
# do things with params
|
||||
|
||||
which allows *my_func* to be called either with two parameters (*old1* and
|
||||
*old2*) or a single one (*new*). Note that the new signature is given
|
||||
last, so that callers get a `TypeError` corresponding to the new signature
|
||||
if the arguments they passed in do not match any signature.
|
||||
"""
|
||||
# Rather than relying on locals() ordering, one could have just used func's
|
||||
# signature (``bound = inspect.signature(func).bind(*args, **kwargs);
|
||||
# bound.apply_defaults(); return bound``) but that is significantly slower.
|
||||
for i, func in enumerate(funcs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except TypeError:
|
||||
if i == len(funcs) - 1:
|
||||
raise
|
||||
|
||||
|
||||
def nargs_error(name, takes, given):
|
||||
"""Generate a TypeError to be raised by function calls with wrong arity."""
|
||||
return TypeError(f"{name}() takes {takes} positional arguments but "
|
||||
f"{given} were given")
|
||||
|
||||
|
||||
def kwarg_error(name, kw):
|
||||
"""
|
||||
Generate a TypeError to be raised by function calls with wrong kwarg.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
The name of the calling function.
|
||||
kw : str or Iterable[str]
|
||||
Either the invalid keyword argument name, or an iterable yielding
|
||||
invalid keyword arguments (e.g., a ``kwargs`` dict).
|
||||
"""
|
||||
if not isinstance(kw, str):
|
||||
kw = next(iter(kw))
|
||||
return TypeError(f"{name}() got an unexpected keyword argument '{kw}'")
|
||||
|
||||
|
||||
def recursive_subclasses(cls):
|
||||
"""Yield *cls* and direct and indirect subclasses of *cls*."""
|
||||
yield cls
|
||||
for subcls in cls.__subclasses__():
|
||||
yield from recursive_subclasses(subcls)
|
||||
|
||||
|
||||
def warn_external(message, category=None):
|
||||
"""
|
||||
`warnings.warn` wrapper that sets *stacklevel* to "outside Matplotlib".
|
||||
|
||||
The original emitter of the warning can be obtained by patching this
|
||||
function back to `warnings.warn`, i.e. ``_api.warn_external =
|
||||
warnings.warn`` (or ``functools.partial(warnings.warn, stacklevel=2)``,
|
||||
etc.).
|
||||
"""
|
||||
kwargs = {}
|
||||
if sys.version_info[:2] >= (3, 12):
|
||||
# Go to Python's `site-packages` or `lib` from an editable install.
|
||||
basedir = pathlib.Path(__file__).parents[2]
|
||||
kwargs['skip_file_prefixes'] = (str(basedir / 'matplotlib'),
|
||||
str(basedir / 'mpl_toolkits'))
|
||||
else:
|
||||
frame = sys._getframe()
|
||||
for stacklevel in itertools.count(1):
|
||||
if frame is None:
|
||||
# when called in embedded context may hit frame is None
|
||||
kwargs['stacklevel'] = stacklevel
|
||||
break
|
||||
if not re.match(r"\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))",
|
||||
# Work around sphinx-gallery not setting __name__.
|
||||
frame.f_globals.get("__name__", "")):
|
||||
kwargs['stacklevel'] = stacklevel
|
||||
break
|
||||
frame = frame.f_back
|
||||
# preemptively break reference cycle between locals and the frame
|
||||
del frame
|
||||
warnings.warn(message, category, **kwargs)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
from collections.abc import Callable, Generator, Iterable, Mapping, Sequence
|
||||
from typing import Any, TypeVar, overload
|
||||
from typing_extensions import Self # < Py 3.11
|
||||
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from .deprecation import ( # noqa: F401, re-exported API
|
||||
deprecated as deprecated,
|
||||
warn_deprecated as warn_deprecated,
|
||||
rename_parameter as rename_parameter,
|
||||
delete_parameter as delete_parameter,
|
||||
make_keyword_only as make_keyword_only,
|
||||
deprecate_method_override as deprecate_method_override,
|
||||
deprecate_privatize_attribute as deprecate_privatize_attribute,
|
||||
suppress_matplotlib_deprecation_warning as suppress_matplotlib_deprecation_warning,
|
||||
MatplotlibDeprecationWarning as MatplotlibDeprecationWarning,
|
||||
)
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
class classproperty(Any):
|
||||
def __init__(
|
||||
self,
|
||||
fget: Callable[[_T], Any],
|
||||
fset: None = ...,
|
||||
fdel: None = ...,
|
||||
doc: str | None = None,
|
||||
): ...
|
||||
@overload
|
||||
def __get__(self, instance: None, owner: None) -> Self: ...
|
||||
@overload
|
||||
def __get__(self, instance: object, owner: type[object]) -> Any: ...
|
||||
@property
|
||||
def fget(self) -> Callable[[_T], Any]: ...
|
||||
|
||||
def check_isinstance(
|
||||
types: type | tuple[type | None, ...], /, **kwargs: Any
|
||||
) -> None: ...
|
||||
def check_in_list(
|
||||
values: Sequence[Any], /, *, _print_supported_values: bool = ..., **kwargs: Any
|
||||
) -> None: ...
|
||||
def check_shape(shape: tuple[int | None, ...], /, **kwargs: NDArray) -> None: ...
|
||||
def check_getitem(mapping: Mapping[Any, Any], /, **kwargs: Any) -> Any: ...
|
||||
def caching_module_getattr(cls: type) -> Callable[[str], Any]: ...
|
||||
@overload
|
||||
def define_aliases(
|
||||
alias_d: dict[str, list[str]], cls: None = ...
|
||||
) -> Callable[[type[_T]], type[_T]]: ...
|
||||
@overload
|
||||
def define_aliases(alias_d: dict[str, list[str]], cls: type[_T]) -> type[_T]: ...
|
||||
def select_matching_signature(
|
||||
funcs: list[Callable], *args: Any, **kwargs: Any
|
||||
) -> Any: ...
|
||||
def nargs_error(name: str, takes: int | str, given: int) -> TypeError: ...
|
||||
def kwarg_error(name: str, kw: str | Iterable[str]) -> TypeError: ...
|
||||
def recursive_subclasses(cls: type) -> Generator[type, None, None]: ...
|
||||
def warn_external(
|
||||
message: str | Warning, category: type[Warning] | None = ...
|
||||
) -> None: ...
|
||||
Binary file not shown.
Binary file not shown.
509
venv/lib/python3.13/site-packages/matplotlib/_api/deprecation.py
Normal file
509
venv/lib/python3.13/site-packages/matplotlib/_api/deprecation.py
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
"""
|
||||
Helper functions for deprecating parts of the Matplotlib API.
|
||||
|
||||
This documentation is only relevant for Matplotlib developers, not for users.
|
||||
|
||||
.. warning::
|
||||
|
||||
This module is for internal use only. Do not use it in your own code.
|
||||
We may change the API at any time with no warning.
|
||||
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import inspect
|
||||
import math
|
||||
import warnings
|
||||
|
||||
|
||||
class MatplotlibDeprecationWarning(DeprecationWarning):
|
||||
"""A class for issuing deprecation warnings for Matplotlib users."""
|
||||
|
||||
|
||||
def _generate_deprecation_warning(
|
||||
since, message='', name='', alternative='', pending=False, obj_type='',
|
||||
addendum='', *, removal=''):
|
||||
if pending:
|
||||
if removal:
|
||||
raise ValueError("A pending deprecation cannot have a scheduled removal")
|
||||
elif removal == '':
|
||||
macro, meso, *_ = since.split('.')
|
||||
removal = f'{macro}.{int(meso) + 2}'
|
||||
if not message:
|
||||
message = (
|
||||
("The %(name)s %(obj_type)s" if obj_type else "%(name)s") +
|
||||
(" will be deprecated in a future version" if pending else
|
||||
(" was deprecated in Matplotlib %(since)s" +
|
||||
(" and will be removed in %(removal)s" if removal else ""))) +
|
||||
"." +
|
||||
(" Use %(alternative)s instead." if alternative else "") +
|
||||
(" %(addendum)s" if addendum else ""))
|
||||
warning_cls = PendingDeprecationWarning if pending else MatplotlibDeprecationWarning
|
||||
return warning_cls(message % dict(
|
||||
func=name, name=name, obj_type=obj_type, since=since, removal=removal,
|
||||
alternative=alternative, addendum=addendum))
|
||||
|
||||
|
||||
def warn_deprecated(
|
||||
since, *, message='', name='', alternative='', pending=False,
|
||||
obj_type='', addendum='', removal=''):
|
||||
"""
|
||||
Display a standardized deprecation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
since : str
|
||||
The release at which this API became deprecated.
|
||||
message : str, optional
|
||||
Override the default deprecation message. The ``%(since)s``,
|
||||
``%(name)s``, ``%(alternative)s``, ``%(obj_type)s``, ``%(addendum)s``,
|
||||
and ``%(removal)s`` format specifiers will be replaced by the values
|
||||
of the respective arguments passed to this function.
|
||||
name : str, optional
|
||||
The name of the deprecated object.
|
||||
alternative : str, optional
|
||||
An alternative API that the user may use in place of the deprecated
|
||||
API. The deprecation warning will tell the user about this alternative
|
||||
if provided.
|
||||
pending : bool, optional
|
||||
If True, uses a PendingDeprecationWarning instead of a
|
||||
DeprecationWarning. Cannot be used together with *removal*.
|
||||
obj_type : str, optional
|
||||
The object type being deprecated.
|
||||
addendum : str, optional
|
||||
Additional text appended directly to the final message.
|
||||
removal : str, optional
|
||||
The expected removal version. With the default (an empty string), a
|
||||
removal version is automatically computed from *since*. Set to other
|
||||
Falsy values to not schedule a removal date. Cannot be used together
|
||||
with *pending*.
|
||||
|
||||
Examples
|
||||
--------
|
||||
::
|
||||
|
||||
# To warn of the deprecation of "matplotlib.name_of_module"
|
||||
warn_deprecated('1.4.0', name='matplotlib.name_of_module',
|
||||
obj_type='module')
|
||||
"""
|
||||
warning = _generate_deprecation_warning(
|
||||
since, message, name, alternative, pending, obj_type, addendum,
|
||||
removal=removal)
|
||||
from . import warn_external
|
||||
warn_external(warning, category=MatplotlibDeprecationWarning)
|
||||
|
||||
|
||||
def deprecated(since, *, message='', name='', alternative='', pending=False,
|
||||
obj_type=None, addendum='', removal=''):
|
||||
"""
|
||||
Decorator to mark a function, a class, or a property as deprecated.
|
||||
|
||||
When deprecating a classmethod, a staticmethod, or a property, the
|
||||
``@deprecated`` decorator should go *under* ``@classmethod`` and
|
||||
``@staticmethod`` (i.e., `deprecated` should directly decorate the
|
||||
underlying callable), but *over* ``@property``.
|
||||
|
||||
When deprecating a class ``C`` intended to be used as a base class in a
|
||||
multiple inheritance hierarchy, ``C`` *must* define an ``__init__`` method
|
||||
(if ``C`` instead inherited its ``__init__`` from its own base class, then
|
||||
``@deprecated`` would mess up ``__init__`` inheritance when installing its
|
||||
own (deprecation-emitting) ``C.__init__``).
|
||||
|
||||
Parameters are the same as for `warn_deprecated`, except that *obj_type*
|
||||
defaults to 'class' if decorating a class, 'attribute' if decorating a
|
||||
property, and 'function' otherwise.
|
||||
|
||||
Examples
|
||||
--------
|
||||
::
|
||||
|
||||
@deprecated('1.4.0')
|
||||
def the_function_to_deprecate():
|
||||
pass
|
||||
"""
|
||||
|
||||
def deprecate(obj, message=message, name=name, alternative=alternative,
|
||||
pending=pending, obj_type=obj_type, addendum=addendum):
|
||||
from matplotlib._api import classproperty
|
||||
|
||||
if isinstance(obj, type):
|
||||
if obj_type is None:
|
||||
obj_type = "class"
|
||||
func = obj.__init__
|
||||
name = name or obj.__name__
|
||||
old_doc = obj.__doc__
|
||||
|
||||
def finalize(wrapper, new_doc):
|
||||
try:
|
||||
obj.__doc__ = new_doc
|
||||
except AttributeError: # Can't set on some extension objects.
|
||||
pass
|
||||
obj.__init__ = functools.wraps(obj.__init__)(wrapper)
|
||||
return obj
|
||||
|
||||
elif isinstance(obj, (property, classproperty)):
|
||||
if obj_type is None:
|
||||
obj_type = "attribute"
|
||||
func = None
|
||||
name = name or obj.fget.__name__
|
||||
old_doc = obj.__doc__
|
||||
|
||||
class _deprecated_property(type(obj)):
|
||||
def __get__(self, instance, owner=None):
|
||||
if instance is not None or owner is not None \
|
||||
and isinstance(self, classproperty):
|
||||
emit_warning()
|
||||
return super().__get__(instance, owner)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if instance is not None:
|
||||
emit_warning()
|
||||
return super().__set__(instance, value)
|
||||
|
||||
def __delete__(self, instance):
|
||||
if instance is not None:
|
||||
emit_warning()
|
||||
return super().__delete__(instance)
|
||||
|
||||
def __set_name__(self, owner, set_name):
|
||||
nonlocal name
|
||||
if name == "<lambda>":
|
||||
name = set_name
|
||||
|
||||
def finalize(_, new_doc):
|
||||
return _deprecated_property(
|
||||
fget=obj.fget, fset=obj.fset, fdel=obj.fdel, doc=new_doc)
|
||||
|
||||
else:
|
||||
if obj_type is None:
|
||||
obj_type = "function"
|
||||
func = obj
|
||||
name = name or obj.__name__
|
||||
old_doc = func.__doc__
|
||||
|
||||
def finalize(wrapper, new_doc):
|
||||
wrapper = functools.wraps(func)(wrapper)
|
||||
wrapper.__doc__ = new_doc
|
||||
return wrapper
|
||||
|
||||
def emit_warning():
|
||||
warn_deprecated(
|
||||
since, message=message, name=name, alternative=alternative,
|
||||
pending=pending, obj_type=obj_type, addendum=addendum,
|
||||
removal=removal)
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
emit_warning()
|
||||
return func(*args, **kwargs)
|
||||
|
||||
old_doc = inspect.cleandoc(old_doc or '').strip('\n')
|
||||
|
||||
notes_header = '\nNotes\n-----'
|
||||
second_arg = ' '.join([t.strip() for t in
|
||||
(message, f"Use {alternative} instead."
|
||||
if alternative else "", addendum) if t])
|
||||
new_doc = (f"[*Deprecated*] {old_doc}\n"
|
||||
f"{notes_header if notes_header not in old_doc else ''}\n"
|
||||
f".. deprecated:: {since}\n"
|
||||
f" {second_arg}")
|
||||
|
||||
if not old_doc:
|
||||
# This is to prevent a spurious 'unexpected unindent' warning from
|
||||
# docutils when the original docstring was blank.
|
||||
new_doc += r'\ '
|
||||
|
||||
return finalize(wrapper, new_doc)
|
||||
|
||||
return deprecate
|
||||
|
||||
|
||||
class deprecate_privatize_attribute:
|
||||
"""
|
||||
Helper to deprecate public access to an attribute (or method).
|
||||
|
||||
This helper should only be used at class scope, as follows::
|
||||
|
||||
class Foo:
|
||||
attr = _deprecate_privatize_attribute(*args, **kwargs)
|
||||
|
||||
where *all* parameters are forwarded to `deprecated`. This form makes
|
||||
``attr`` a property which forwards read and write access to ``self._attr``
|
||||
(same name but with a leading underscore), with a deprecation warning.
|
||||
Note that the attribute name is derived from *the name this helper is
|
||||
assigned to*. This helper also works for deprecating methods.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.deprecator = deprecated(*args, **kwargs)
|
||||
|
||||
def __set_name__(self, owner, name):
|
||||
setattr(owner, name, self.deprecator(
|
||||
property(lambda self: getattr(self, f"_{name}"),
|
||||
lambda self, value: setattr(self, f"_{name}", value)),
|
||||
name=name))
|
||||
|
||||
|
||||
# Used by _copy_docstring_and_deprecators to redecorate pyplot wrappers and
|
||||
# boilerplate.py to retrieve original signatures. It may seem natural to store
|
||||
# this information as an attribute on the wrapper, but if the wrapper gets
|
||||
# itself functools.wraps()ed, then such attributes are silently propagated to
|
||||
# the outer wrapper, which is not desired.
|
||||
DECORATORS = {}
|
||||
|
||||
|
||||
def rename_parameter(since, old, new, func=None):
|
||||
"""
|
||||
Decorator indicating that parameter *old* of *func* is renamed to *new*.
|
||||
|
||||
The actual implementation of *func* should use *new*, not *old*. If *old*
|
||||
is passed to *func*, a DeprecationWarning is emitted, and its value is
|
||||
used, even if *new* is also passed by keyword (this is to simplify pyplot
|
||||
wrapper functions, which always pass *new* explicitly to the Axes method).
|
||||
If *new* is also passed but positionally, a TypeError will be raised by the
|
||||
underlying function during argument binding.
|
||||
|
||||
Examples
|
||||
--------
|
||||
::
|
||||
|
||||
@_api.rename_parameter("3.1", "bad_name", "good_name")
|
||||
def func(good_name): ...
|
||||
"""
|
||||
|
||||
decorator = functools.partial(rename_parameter, since, old, new)
|
||||
|
||||
if func is None:
|
||||
return decorator
|
||||
|
||||
signature = inspect.signature(func)
|
||||
assert old not in signature.parameters, (
|
||||
f"Matplotlib internal error: {old!r} cannot be a parameter for "
|
||||
f"{func.__name__}()")
|
||||
assert new in signature.parameters, (
|
||||
f"Matplotlib internal error: {new!r} must be a parameter for "
|
||||
f"{func.__name__}()")
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
if old in kwargs:
|
||||
warn_deprecated(
|
||||
since, message=f"The {old!r} parameter of {func.__name__}() "
|
||||
f"has been renamed {new!r} since Matplotlib {since}; support "
|
||||
f"for the old name will be dropped in %(removal)s.")
|
||||
kwargs[new] = kwargs.pop(old)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# wrapper() must keep the same documented signature as func(): if we
|
||||
# instead made both *old* and *new* appear in wrapper()'s signature, they
|
||||
# would both show up in the pyplot function for an Axes method as well and
|
||||
# pyplot would explicitly pass both arguments to the Axes method.
|
||||
|
||||
DECORATORS[wrapper] = decorator
|
||||
return wrapper
|
||||
|
||||
|
||||
class _deprecated_parameter_class:
|
||||
def __repr__(self):
|
||||
return "<deprecated parameter>"
|
||||
|
||||
|
||||
_deprecated_parameter = _deprecated_parameter_class()
|
||||
|
||||
|
||||
def delete_parameter(since, name, func=None, **kwargs):
|
||||
"""
|
||||
Decorator indicating that parameter *name* of *func* is being deprecated.
|
||||
|
||||
The actual implementation of *func* should keep the *name* parameter in its
|
||||
signature, or accept a ``**kwargs`` argument (through which *name* would be
|
||||
passed).
|
||||
|
||||
Parameters that come after the deprecated parameter effectively become
|
||||
keyword-only (as they cannot be passed positionally without triggering the
|
||||
DeprecationWarning on the deprecated parameter), and should be marked as
|
||||
such after the deprecation period has passed and the deprecated parameter
|
||||
is removed.
|
||||
|
||||
Parameters other than *since*, *name*, and *func* are keyword-only and
|
||||
forwarded to `.warn_deprecated`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
::
|
||||
|
||||
@_api.delete_parameter("3.1", "unused")
|
||||
def func(used_arg, other_arg, unused, more_args): ...
|
||||
"""
|
||||
|
||||
decorator = functools.partial(delete_parameter, since, name, **kwargs)
|
||||
|
||||
if func is None:
|
||||
return decorator
|
||||
|
||||
signature = inspect.signature(func)
|
||||
# Name of `**kwargs` parameter of the decorated function, typically
|
||||
# "kwargs" if such a parameter exists, or None if the decorated function
|
||||
# doesn't accept `**kwargs`.
|
||||
kwargs_name = next((param.name for param in signature.parameters.values()
|
||||
if param.kind == inspect.Parameter.VAR_KEYWORD), None)
|
||||
if name in signature.parameters:
|
||||
kind = signature.parameters[name].kind
|
||||
is_varargs = kind is inspect.Parameter.VAR_POSITIONAL
|
||||
is_varkwargs = kind is inspect.Parameter.VAR_KEYWORD
|
||||
if not is_varargs and not is_varkwargs:
|
||||
name_idx = (
|
||||
# Deprecated parameter can't be passed positionally.
|
||||
math.inf if kind is inspect.Parameter.KEYWORD_ONLY
|
||||
# If call site has no more than this number of parameters, the
|
||||
# deprecated parameter can't have been passed positionally.
|
||||
else [*signature.parameters].index(name))
|
||||
func.__signature__ = signature = signature.replace(parameters=[
|
||||
param.replace(default=_deprecated_parameter)
|
||||
if param.name == name else param
|
||||
for param in signature.parameters.values()])
|
||||
else:
|
||||
name_idx = -1 # Deprecated parameter can always have been passed.
|
||||
else:
|
||||
is_varargs = is_varkwargs = False
|
||||
# Deprecated parameter can't be passed positionally.
|
||||
name_idx = math.inf
|
||||
assert kwargs_name, (
|
||||
f"Matplotlib internal error: {name!r} must be a parameter for "
|
||||
f"{func.__name__}()")
|
||||
|
||||
addendum = kwargs.pop('addendum', None)
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*inner_args, **inner_kwargs):
|
||||
if len(inner_args) <= name_idx and name not in inner_kwargs:
|
||||
# Early return in the simple, non-deprecated case (much faster than
|
||||
# calling bind()).
|
||||
return func(*inner_args, **inner_kwargs)
|
||||
arguments = signature.bind(*inner_args, **inner_kwargs).arguments
|
||||
if is_varargs and arguments.get(name):
|
||||
warn_deprecated(
|
||||
since, message=f"Additional positional arguments to "
|
||||
f"{func.__name__}() are deprecated since %(since)s and "
|
||||
f"support for them will be removed in %(removal)s.")
|
||||
elif is_varkwargs and arguments.get(name):
|
||||
warn_deprecated(
|
||||
since, message=f"Additional keyword arguments to "
|
||||
f"{func.__name__}() are deprecated since %(since)s and "
|
||||
f"support for them will be removed in %(removal)s.")
|
||||
# We cannot just check `name not in arguments` because the pyplot
|
||||
# wrappers always pass all arguments explicitly.
|
||||
elif any(name in d and d[name] != _deprecated_parameter
|
||||
for d in [arguments, arguments.get(kwargs_name, {})]):
|
||||
deprecation_addendum = (
|
||||
f"If any parameter follows {name!r}, they should be passed as "
|
||||
f"keyword, not positionally.")
|
||||
warn_deprecated(
|
||||
since,
|
||||
name=repr(name),
|
||||
obj_type=f"parameter of {func.__name__}()",
|
||||
addendum=(addendum + " " + deprecation_addendum) if addendum
|
||||
else deprecation_addendum,
|
||||
**kwargs)
|
||||
return func(*inner_args, **inner_kwargs)
|
||||
|
||||
DECORATORS[wrapper] = decorator
|
||||
return wrapper
|
||||
|
||||
|
||||
def make_keyword_only(since, name, func=None):
|
||||
"""
|
||||
Decorator indicating that passing parameter *name* (or any of the following
|
||||
ones) positionally to *func* is being deprecated.
|
||||
|
||||
When used on a method that has a pyplot wrapper, this should be the
|
||||
outermost decorator, so that :file:`boilerplate.py` can access the original
|
||||
signature.
|
||||
"""
|
||||
|
||||
decorator = functools.partial(make_keyword_only, since, name)
|
||||
|
||||
if func is None:
|
||||
return decorator
|
||||
|
||||
signature = inspect.signature(func)
|
||||
POK = inspect.Parameter.POSITIONAL_OR_KEYWORD
|
||||
KWO = inspect.Parameter.KEYWORD_ONLY
|
||||
assert (name in signature.parameters
|
||||
and signature.parameters[name].kind == POK), (
|
||||
f"Matplotlib internal error: {name!r} must be a positional-or-keyword "
|
||||
f"parameter for {func.__name__}(). If this error happens on a function with a "
|
||||
f"pyplot wrapper, make sure make_keyword_only() is the outermost decorator.")
|
||||
names = [*signature.parameters]
|
||||
name_idx = names.index(name)
|
||||
kwonly = [name for name in names[name_idx:]
|
||||
if signature.parameters[name].kind == POK]
|
||||
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
# Don't use signature.bind here, as it would fail when stacked with
|
||||
# rename_parameter and an "old" argument name is passed in
|
||||
# (signature.bind would fail, but the actual call would succeed).
|
||||
if len(args) > name_idx:
|
||||
warn_deprecated(
|
||||
since, message="Passing the %(name)s %(obj_type)s "
|
||||
"positionally is deprecated since Matplotlib %(since)s; the "
|
||||
"parameter will become keyword-only in %(removal)s.",
|
||||
name=name, obj_type=f"parameter of {func.__name__}()")
|
||||
return func(*args, **kwargs)
|
||||
|
||||
# Don't modify *func*'s signature, as boilerplate.py needs it.
|
||||
wrapper.__signature__ = signature.replace(parameters=[
|
||||
param.replace(kind=KWO) if param.name in kwonly else param
|
||||
for param in signature.parameters.values()])
|
||||
DECORATORS[wrapper] = decorator
|
||||
return wrapper
|
||||
|
||||
|
||||
def deprecate_method_override(method, obj, *, allow_empty=False, **kwargs):
|
||||
"""
|
||||
Return ``obj.method`` with a deprecation if it was overridden, else None.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
method
|
||||
An unbound method, i.e. an expression of the form
|
||||
``Class.method_name``. Remember that within the body of a method, one
|
||||
can always use ``__class__`` to refer to the class that is currently
|
||||
being defined.
|
||||
obj
|
||||
Either an object of the class where *method* is defined, or a subclass
|
||||
of that class.
|
||||
allow_empty : bool, default: False
|
||||
Whether to allow overrides by "empty" methods without emitting a
|
||||
warning.
|
||||
**kwargs
|
||||
Additional parameters passed to `warn_deprecated` to generate the
|
||||
deprecation warning; must at least include the "since" key.
|
||||
"""
|
||||
|
||||
def empty(): pass
|
||||
def empty_with_docstring(): """doc"""
|
||||
|
||||
name = method.__name__
|
||||
bound_child = getattr(obj, name)
|
||||
bound_base = (
|
||||
method # If obj is a class, then we need to use unbound methods.
|
||||
if isinstance(bound_child, type(empty)) and isinstance(obj, type)
|
||||
else method.__get__(obj))
|
||||
if (bound_child != bound_base
|
||||
and (not allow_empty
|
||||
or (getattr(getattr(bound_child, "__code__", None),
|
||||
"co_code", None)
|
||||
not in [empty.__code__.co_code,
|
||||
empty_with_docstring.__code__.co_code]))):
|
||||
warn_deprecated(**{"name": name, "obj_type": "method", **kwargs})
|
||||
return bound_child
|
||||
return None
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def suppress_matplotlib_deprecation_warning():
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", MatplotlibDeprecationWarning)
|
||||
yield
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
from collections.abc import Callable
|
||||
import contextlib
|
||||
from typing import Any, Literal, ParamSpec, TypedDict, TypeVar, overload
|
||||
from typing_extensions import (
|
||||
Unpack, # < Py 3.11
|
||||
)
|
||||
|
||||
_P = ParamSpec("_P")
|
||||
_R = TypeVar("_R")
|
||||
_T = TypeVar("_T")
|
||||
|
||||
class MatplotlibDeprecationWarning(DeprecationWarning): ...
|
||||
|
||||
class DeprecationKwargs(TypedDict, total=False):
|
||||
message: str
|
||||
alternative: str
|
||||
pending: bool
|
||||
obj_type: str
|
||||
addendum: str
|
||||
removal: str | Literal[False]
|
||||
|
||||
class NamedDeprecationKwargs(DeprecationKwargs, total=False):
|
||||
name: str
|
||||
|
||||
def warn_deprecated(since: str, **kwargs: Unpack[NamedDeprecationKwargs]) -> None: ...
|
||||
def deprecated(
|
||||
since: str, **kwargs: Unpack[NamedDeprecationKwargs]
|
||||
) -> Callable[[_T], _T]: ...
|
||||
|
||||
class deprecate_privatize_attribute(Any):
|
||||
def __init__(self, since: str, **kwargs: Unpack[NamedDeprecationKwargs]): ...
|
||||
def __set_name__(self, owner: type[object], name: str) -> None: ...
|
||||
|
||||
DECORATORS: dict[Callable, Callable] = ...
|
||||
|
||||
@overload
|
||||
def rename_parameter(
|
||||
since: str, old: str, new: str, func: None = ...
|
||||
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
|
||||
@overload
|
||||
def rename_parameter(
|
||||
since: str, old: str, new: str, func: Callable[_P, _R]
|
||||
) -> Callable[_P, _R]: ...
|
||||
|
||||
class _deprecated_parameter_class: ...
|
||||
|
||||
_deprecated_parameter: _deprecated_parameter_class
|
||||
|
||||
@overload
|
||||
def delete_parameter(
|
||||
since: str, name: str, func: None = ..., **kwargs: Unpack[DeprecationKwargs]
|
||||
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
|
||||
@overload
|
||||
def delete_parameter(
|
||||
since: str, name: str, func: Callable[_P, _R], **kwargs: Unpack[DeprecationKwargs]
|
||||
) -> Callable[_P, _R]: ...
|
||||
@overload
|
||||
def make_keyword_only(
|
||||
since: str, name: str, func: None = ...
|
||||
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ...
|
||||
@overload
|
||||
def make_keyword_only(
|
||||
since: str, name: str, func: Callable[_P, _R]
|
||||
) -> Callable[_P, _R]: ...
|
||||
def deprecate_method_override(
|
||||
method: Callable[_P, _R],
|
||||
obj: object | type,
|
||||
*,
|
||||
allow_empty: bool = ...,
|
||||
since: str,
|
||||
**kwargs: Unpack[NamedDeprecationKwargs]
|
||||
) -> Callable[_P, _R]: ...
|
||||
def suppress_matplotlib_deprecation_warning() -> (
|
||||
contextlib.AbstractContextManager[None]
|
||||
): ...
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
def blocking_input_loop(figure, event_names, timeout, handler):
|
||||
"""
|
||||
Run *figure*'s event loop while listening to interactive events.
|
||||
|
||||
The events listed in *event_names* are passed to *handler*.
|
||||
|
||||
This function is used to implement `.Figure.waitforbuttonpress`,
|
||||
`.Figure.ginput`, and `.Axes.clabel`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
figure : `~matplotlib.figure.Figure`
|
||||
event_names : list of str
|
||||
The names of the events passed to *handler*.
|
||||
timeout : float
|
||||
If positive, the event loop is stopped after *timeout* seconds.
|
||||
handler : Callable[[Event], Any]
|
||||
Function called for each event; it can force an early exit of the event
|
||||
loop by calling ``canvas.stop_event_loop()``.
|
||||
"""
|
||||
if figure.canvas.manager:
|
||||
figure.show() # Ensure that the figure is shown if we are managing it.
|
||||
# Connect the events to the on_event function call.
|
||||
cids = [figure.canvas.mpl_connect(name, handler) for name in event_names]
|
||||
try:
|
||||
figure.canvas.start_event_loop(timeout) # Start event loop.
|
||||
finally: # Run even on exception like ctrl-c.
|
||||
# Disconnect the callbacks.
|
||||
for cid in cids:
|
||||
figure.canvas.mpl_disconnect(cid)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
|||
def display_is_valid() -> bool: ...
|
||||
def xdisplay_is_valid() -> bool: ...
|
||||
|
||||
def Win32_GetForegroundWindow() -> int | None: ...
|
||||
def Win32_SetForegroundWindow(hwnd: int) -> None: ...
|
||||
def Win32_SetProcessDpiAwareness_max() -> None: ...
|
||||
def Win32_SetCurrentProcessExplicitAppUserModelID(appid: str) -> None: ...
|
||||
def Win32_GetCurrentProcessExplicitAppUserModelID() -> str | None: ...
|
||||
1460
venv/lib/python3.13/site-packages/matplotlib/_cm.py
Normal file
1460
venv/lib/python3.13/site-packages/matplotlib/_cm.py
Normal file
File diff suppressed because it is too large
Load diff
1312
venv/lib/python3.13/site-packages/matplotlib/_cm_bivar.py
Normal file
1312
venv/lib/python3.13/site-packages/matplotlib/_cm_bivar.py
Normal file
File diff suppressed because it is too large
Load diff
2847
venv/lib/python3.13/site-packages/matplotlib/_cm_listed.py
Normal file
2847
venv/lib/python3.13/site-packages/matplotlib/_cm_listed.py
Normal file
File diff suppressed because it is too large
Load diff
166
venv/lib/python3.13/site-packages/matplotlib/_cm_multivar.py
Normal file
166
venv/lib/python3.13/site-packages/matplotlib/_cm_multivar.py
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# auto-generated by https://github.com/trygvrad/multivariate_colormaps
|
||||
# date: 2024-05-28
|
||||
|
||||
from .colors import LinearSegmentedColormap, MultivarColormap
|
||||
import matplotlib as mpl
|
||||
_LUTSIZE = mpl.rcParams['image.lut']
|
||||
|
||||
_2VarAddA0_data = [[0.000, 0.000, 0.000],
|
||||
[0.020, 0.026, 0.031],
|
||||
[0.049, 0.068, 0.085],
|
||||
[0.075, 0.107, 0.135],
|
||||
[0.097, 0.144, 0.183],
|
||||
[0.116, 0.178, 0.231],
|
||||
[0.133, 0.212, 0.279],
|
||||
[0.148, 0.244, 0.326],
|
||||
[0.161, 0.276, 0.374],
|
||||
[0.173, 0.308, 0.422],
|
||||
[0.182, 0.339, 0.471],
|
||||
[0.190, 0.370, 0.521],
|
||||
[0.197, 0.400, 0.572],
|
||||
[0.201, 0.431, 0.623],
|
||||
[0.204, 0.461, 0.675],
|
||||
[0.204, 0.491, 0.728],
|
||||
[0.202, 0.520, 0.783],
|
||||
[0.197, 0.549, 0.838],
|
||||
[0.187, 0.577, 0.895]]
|
||||
|
||||
_2VarAddA1_data = [[0.000, 0.000, 0.000],
|
||||
[0.030, 0.023, 0.018],
|
||||
[0.079, 0.060, 0.043],
|
||||
[0.125, 0.093, 0.065],
|
||||
[0.170, 0.123, 0.083],
|
||||
[0.213, 0.151, 0.098],
|
||||
[0.255, 0.177, 0.110],
|
||||
[0.298, 0.202, 0.120],
|
||||
[0.341, 0.226, 0.128],
|
||||
[0.384, 0.249, 0.134],
|
||||
[0.427, 0.271, 0.138],
|
||||
[0.472, 0.292, 0.141],
|
||||
[0.517, 0.313, 0.142],
|
||||
[0.563, 0.333, 0.141],
|
||||
[0.610, 0.353, 0.139],
|
||||
[0.658, 0.372, 0.134],
|
||||
[0.708, 0.390, 0.127],
|
||||
[0.759, 0.407, 0.118],
|
||||
[0.813, 0.423, 0.105]]
|
||||
|
||||
_2VarSubA0_data = [[1.000, 1.000, 1.000],
|
||||
[0.959, 0.973, 0.986],
|
||||
[0.916, 0.948, 0.974],
|
||||
[0.874, 0.923, 0.965],
|
||||
[0.832, 0.899, 0.956],
|
||||
[0.790, 0.875, 0.948],
|
||||
[0.748, 0.852, 0.940],
|
||||
[0.707, 0.829, 0.934],
|
||||
[0.665, 0.806, 0.927],
|
||||
[0.624, 0.784, 0.921],
|
||||
[0.583, 0.762, 0.916],
|
||||
[0.541, 0.740, 0.910],
|
||||
[0.500, 0.718, 0.905],
|
||||
[0.457, 0.697, 0.901],
|
||||
[0.414, 0.675, 0.896],
|
||||
[0.369, 0.652, 0.892],
|
||||
[0.320, 0.629, 0.888],
|
||||
[0.266, 0.604, 0.884],
|
||||
[0.199, 0.574, 0.881]]
|
||||
|
||||
_2VarSubA1_data = [[1.000, 1.000, 1.000],
|
||||
[0.982, 0.967, 0.955],
|
||||
[0.966, 0.935, 0.908],
|
||||
[0.951, 0.902, 0.860],
|
||||
[0.937, 0.870, 0.813],
|
||||
[0.923, 0.838, 0.765],
|
||||
[0.910, 0.807, 0.718],
|
||||
[0.898, 0.776, 0.671],
|
||||
[0.886, 0.745, 0.624],
|
||||
[0.874, 0.714, 0.577],
|
||||
[0.862, 0.683, 0.530],
|
||||
[0.851, 0.653, 0.483],
|
||||
[0.841, 0.622, 0.435],
|
||||
[0.831, 0.592, 0.388],
|
||||
[0.822, 0.561, 0.340],
|
||||
[0.813, 0.530, 0.290],
|
||||
[0.806, 0.498, 0.239],
|
||||
[0.802, 0.464, 0.184],
|
||||
[0.801, 0.426, 0.119]]
|
||||
|
||||
_3VarAddA0_data = [[0.000, 0.000, 0.000],
|
||||
[0.018, 0.023, 0.028],
|
||||
[0.040, 0.056, 0.071],
|
||||
[0.059, 0.087, 0.110],
|
||||
[0.074, 0.114, 0.147],
|
||||
[0.086, 0.139, 0.183],
|
||||
[0.095, 0.163, 0.219],
|
||||
[0.101, 0.187, 0.255],
|
||||
[0.105, 0.209, 0.290],
|
||||
[0.107, 0.230, 0.326],
|
||||
[0.105, 0.251, 0.362],
|
||||
[0.101, 0.271, 0.398],
|
||||
[0.091, 0.291, 0.434],
|
||||
[0.075, 0.309, 0.471],
|
||||
[0.046, 0.325, 0.507],
|
||||
[0.021, 0.341, 0.546],
|
||||
[0.021, 0.363, 0.584],
|
||||
[0.022, 0.385, 0.622],
|
||||
[0.023, 0.408, 0.661]]
|
||||
|
||||
_3VarAddA1_data = [[0.000, 0.000, 0.000],
|
||||
[0.020, 0.024, 0.016],
|
||||
[0.047, 0.058, 0.034],
|
||||
[0.072, 0.088, 0.048],
|
||||
[0.093, 0.116, 0.059],
|
||||
[0.113, 0.142, 0.067],
|
||||
[0.131, 0.167, 0.071],
|
||||
[0.149, 0.190, 0.074],
|
||||
[0.166, 0.213, 0.074],
|
||||
[0.182, 0.235, 0.072],
|
||||
[0.198, 0.256, 0.068],
|
||||
[0.215, 0.276, 0.061],
|
||||
[0.232, 0.296, 0.051],
|
||||
[0.249, 0.314, 0.037],
|
||||
[0.270, 0.330, 0.018],
|
||||
[0.288, 0.347, 0.000],
|
||||
[0.302, 0.369, 0.000],
|
||||
[0.315, 0.391, 0.000],
|
||||
[0.328, 0.414, 0.000]]
|
||||
|
||||
_3VarAddA2_data = [[0.000, 0.000, 0.000],
|
||||
[0.029, 0.020, 0.023],
|
||||
[0.072, 0.045, 0.055],
|
||||
[0.111, 0.067, 0.084],
|
||||
[0.148, 0.085, 0.109],
|
||||
[0.184, 0.101, 0.133],
|
||||
[0.219, 0.115, 0.155],
|
||||
[0.254, 0.127, 0.176],
|
||||
[0.289, 0.138, 0.195],
|
||||
[0.323, 0.147, 0.214],
|
||||
[0.358, 0.155, 0.232],
|
||||
[0.393, 0.161, 0.250],
|
||||
[0.429, 0.166, 0.267],
|
||||
[0.467, 0.169, 0.283],
|
||||
[0.507, 0.168, 0.298],
|
||||
[0.546, 0.168, 0.313],
|
||||
[0.580, 0.172, 0.328],
|
||||
[0.615, 0.175, 0.341],
|
||||
[0.649, 0.178, 0.355]]
|
||||
|
||||
cmaps = {
|
||||
name: LinearSegmentedColormap.from_list(name, data, _LUTSIZE) for name, data in [
|
||||
('2VarAddA0', _2VarAddA0_data),
|
||||
('2VarAddA1', _2VarAddA1_data),
|
||||
('2VarSubA0', _2VarSubA0_data),
|
||||
('2VarSubA1', _2VarSubA1_data),
|
||||
('3VarAddA0', _3VarAddA0_data),
|
||||
('3VarAddA1', _3VarAddA1_data),
|
||||
('3VarAddA2', _3VarAddA2_data),
|
||||
]}
|
||||
|
||||
cmap_families = {
|
||||
'2VarAddA': MultivarColormap([cmaps[f'2VarAddA{i}'] for i in range(2)],
|
||||
'sRGB_add', name='2VarAddA'),
|
||||
'2VarSubA': MultivarColormap([cmaps[f'2VarSubA{i}'] for i in range(2)],
|
||||
'sRGB_sub', name='2VarSubA'),
|
||||
'3VarAddA': MultivarColormap([cmaps[f'3VarAddA{i}'] for i in range(3)],
|
||||
'sRGB_add', name='3VarAddA'),
|
||||
}
|
||||
1141
venv/lib/python3.13/site-packages/matplotlib/_color_data.py
Normal file
1141
venv/lib/python3.13/site-packages/matplotlib/_color_data.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,6 @@
|
|||
from .typing import ColorType
|
||||
|
||||
BASE_COLORS: dict[str, ColorType]
|
||||
TABLEAU_COLORS: dict[str, ColorType]
|
||||
XKCD_COLORS: dict[str, ColorType]
|
||||
CSS4_COLORS: dict[str, ColorType]
|
||||
|
|
@ -0,0 +1,805 @@
|
|||
"""
|
||||
Adjust subplot layouts so that there are no overlapping Axes or Axes
|
||||
decorations. All Axes decorations are dealt with (labels, ticks, titles,
|
||||
ticklabels) and some dependent artists are also dealt with (colorbar,
|
||||
suptitle).
|
||||
|
||||
Layout is done via `~matplotlib.gridspec`, with one constraint per gridspec,
|
||||
so it is possible to have overlapping Axes if the gridspecs overlap (i.e.
|
||||
using `~matplotlib.gridspec.GridSpecFromSubplotSpec`). Axes placed using
|
||||
``figure.subplots()`` or ``figure.add_subplots()`` will participate in the
|
||||
layout. Axes manually placed via ``figure.add_axes()`` will not.
|
||||
|
||||
See Tutorial: :ref:`constrainedlayout_guide`
|
||||
|
||||
General idea:
|
||||
-------------
|
||||
|
||||
First, a figure has a gridspec that divides the figure into nrows and ncols,
|
||||
with heights and widths set by ``height_ratios`` and ``width_ratios``,
|
||||
often just set to 1 for an equal grid.
|
||||
|
||||
Subplotspecs that are derived from this gridspec can contain either a
|
||||
``SubPanel``, a ``GridSpecFromSubplotSpec``, or an ``Axes``. The ``SubPanel``
|
||||
and ``GridSpecFromSubplotSpec`` are dealt with recursively and each contain an
|
||||
analogous layout.
|
||||
|
||||
Each ``GridSpec`` has a ``_layoutgrid`` attached to it. The ``_layoutgrid``
|
||||
has the same logical layout as the ``GridSpec``. Each row of the grid spec
|
||||
has a top and bottom "margin" and each column has a left and right "margin".
|
||||
The "inner" height of each row is constrained to be the same (or as modified
|
||||
by ``height_ratio``), and the "inner" width of each column is
|
||||
constrained to be the same (as modified by ``width_ratio``), where "inner"
|
||||
is the width or height of each column/row minus the size of the margins.
|
||||
|
||||
Then the size of the margins for each row and column are determined as the
|
||||
max width of the decorators on each Axes that has decorators in that margin.
|
||||
For instance, a normal Axes would have a left margin that includes the
|
||||
left ticklabels, and the ylabel if it exists. The right margin may include a
|
||||
colorbar, the bottom margin the xaxis decorations, and the top margin the
|
||||
title.
|
||||
|
||||
With these constraints, the solver then finds appropriate bounds for the
|
||||
columns and rows. It's possible that the margins take up the whole figure,
|
||||
in which case the algorithm is not applied and a warning is raised.
|
||||
|
||||
See the tutorial :ref:`constrainedlayout_guide`
|
||||
for more discussion of the algorithm with examples.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _api, artist as martist
|
||||
import matplotlib.transforms as mtransforms
|
||||
import matplotlib._layoutgrid as mlayoutgrid
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
######################################################
|
||||
def do_constrained_layout(fig, h_pad, w_pad,
|
||||
hspace=None, wspace=None, rect=(0, 0, 1, 1),
|
||||
compress=False):
|
||||
"""
|
||||
Do the constrained_layout. Called at draw time in
|
||||
``figure.constrained_layout()``
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fig : `~matplotlib.figure.Figure`
|
||||
`.Figure` instance to do the layout in.
|
||||
|
||||
h_pad, w_pad : float
|
||||
Padding around the Axes elements in figure-normalized units.
|
||||
|
||||
hspace, wspace : float
|
||||
Fraction of the figure to dedicate to space between the
|
||||
Axes. These are evenly spread between the gaps between the Axes.
|
||||
A value of 0.2 for a three-column layout would have a space
|
||||
of 0.1 of the figure width between each column.
|
||||
If h/wspace < h/w_pad, then the pads are used instead.
|
||||
|
||||
rect : tuple of 4 floats
|
||||
Rectangle in figure coordinates to perform constrained layout in
|
||||
[left, bottom, width, height], each from 0-1.
|
||||
|
||||
compress : bool
|
||||
Whether to shift Axes so that white space in between them is
|
||||
removed. This is useful for simple grids of fixed-aspect Axes (e.g.
|
||||
a grid of images).
|
||||
|
||||
Returns
|
||||
-------
|
||||
layoutgrid : private debugging structure
|
||||
"""
|
||||
|
||||
renderer = fig._get_renderer()
|
||||
# make layoutgrid tree...
|
||||
layoutgrids = make_layoutgrids(fig, None, rect=rect)
|
||||
if not layoutgrids['hasgrids']:
|
||||
_api.warn_external('There are no gridspecs with layoutgrids. '
|
||||
'Possibly did not call parent GridSpec with the'
|
||||
' "figure" keyword')
|
||||
return
|
||||
|
||||
for _ in range(2):
|
||||
# do the algorithm twice. This has to be done because decorations
|
||||
# change size after the first re-position (i.e. x/yticklabels get
|
||||
# larger/smaller). This second reposition tends to be much milder,
|
||||
# so doing twice makes things work OK.
|
||||
|
||||
# make margins for all the Axes and subfigures in the
|
||||
# figure. Add margins for colorbars...
|
||||
make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad,
|
||||
w_pad=w_pad, hspace=hspace, wspace=wspace)
|
||||
make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad,
|
||||
w_pad=w_pad)
|
||||
|
||||
# if a layout is such that a columns (or rows) margin has no
|
||||
# constraints, we need to make all such instances in the grid
|
||||
# match in margin size.
|
||||
match_submerged_margins(layoutgrids, fig)
|
||||
|
||||
# update all the variables in the layout.
|
||||
layoutgrids[fig].update_variables()
|
||||
|
||||
warn_collapsed = ('constrained_layout not applied because '
|
||||
'axes sizes collapsed to zero. Try making '
|
||||
'figure larger or Axes decorations smaller.')
|
||||
if check_no_collapsed_axes(layoutgrids, fig):
|
||||
reposition_axes(layoutgrids, fig, renderer, h_pad=h_pad,
|
||||
w_pad=w_pad, hspace=hspace, wspace=wspace)
|
||||
if compress:
|
||||
layoutgrids = compress_fixed_aspect(layoutgrids, fig)
|
||||
layoutgrids[fig].update_variables()
|
||||
if check_no_collapsed_axes(layoutgrids, fig):
|
||||
reposition_axes(layoutgrids, fig, renderer, h_pad=h_pad,
|
||||
w_pad=w_pad, hspace=hspace, wspace=wspace)
|
||||
else:
|
||||
_api.warn_external(warn_collapsed)
|
||||
|
||||
if ((suptitle := fig._suptitle) is not None and
|
||||
suptitle.get_in_layout() and suptitle._autopos):
|
||||
x, _ = suptitle.get_position()
|
||||
suptitle.set_position(
|
||||
(x, layoutgrids[fig].get_inner_bbox().y1 + h_pad))
|
||||
suptitle.set_verticalalignment('bottom')
|
||||
else:
|
||||
_api.warn_external(warn_collapsed)
|
||||
reset_margins(layoutgrids, fig)
|
||||
return layoutgrids
|
||||
|
||||
|
||||
def make_layoutgrids(fig, layoutgrids, rect=(0, 0, 1, 1)):
|
||||
"""
|
||||
Make the layoutgrid tree.
|
||||
|
||||
(Sub)Figures get a layoutgrid so we can have figure margins.
|
||||
|
||||
Gridspecs that are attached to Axes get a layoutgrid so Axes
|
||||
can have margins.
|
||||
"""
|
||||
|
||||
if layoutgrids is None:
|
||||
layoutgrids = dict()
|
||||
layoutgrids['hasgrids'] = False
|
||||
if not hasattr(fig, '_parent'):
|
||||
# top figure; pass rect as parent to allow user-specified
|
||||
# margins
|
||||
layoutgrids[fig] = mlayoutgrid.LayoutGrid(parent=rect, name='figlb')
|
||||
else:
|
||||
# subfigure
|
||||
gs = fig._subplotspec.get_gridspec()
|
||||
# it is possible the gridspec containing this subfigure hasn't
|
||||
# been added to the tree yet:
|
||||
layoutgrids = make_layoutgrids_gs(layoutgrids, gs)
|
||||
# add the layoutgrid for the subfigure:
|
||||
parentlb = layoutgrids[gs]
|
||||
layoutgrids[fig] = mlayoutgrid.LayoutGrid(
|
||||
parent=parentlb,
|
||||
name='panellb',
|
||||
parent_inner=True,
|
||||
nrows=1, ncols=1,
|
||||
parent_pos=(fig._subplotspec.rowspan,
|
||||
fig._subplotspec.colspan))
|
||||
# recursively do all subfigures in this figure...
|
||||
for sfig in fig.subfigs:
|
||||
layoutgrids = make_layoutgrids(sfig, layoutgrids)
|
||||
|
||||
# for each Axes at the local level add its gridspec:
|
||||
for ax in fig._localaxes:
|
||||
gs = ax.get_gridspec()
|
||||
if gs is not None:
|
||||
layoutgrids = make_layoutgrids_gs(layoutgrids, gs)
|
||||
|
||||
return layoutgrids
|
||||
|
||||
|
||||
def make_layoutgrids_gs(layoutgrids, gs):
|
||||
"""
|
||||
Make the layoutgrid for a gridspec (and anything nested in the gridspec)
|
||||
"""
|
||||
|
||||
if gs in layoutgrids or gs.figure is None:
|
||||
return layoutgrids
|
||||
# in order to do constrained_layout there has to be at least *one*
|
||||
# gridspec in the tree:
|
||||
layoutgrids['hasgrids'] = True
|
||||
if not hasattr(gs, '_subplot_spec'):
|
||||
# normal gridspec
|
||||
parent = layoutgrids[gs.figure]
|
||||
layoutgrids[gs] = mlayoutgrid.LayoutGrid(
|
||||
parent=parent,
|
||||
parent_inner=True,
|
||||
name='gridspec',
|
||||
ncols=gs._ncols, nrows=gs._nrows,
|
||||
width_ratios=gs.get_width_ratios(),
|
||||
height_ratios=gs.get_height_ratios())
|
||||
else:
|
||||
# this is a gridspecfromsubplotspec:
|
||||
subplot_spec = gs._subplot_spec
|
||||
parentgs = subplot_spec.get_gridspec()
|
||||
# if a nested gridspec it is possible the parent is not in there yet:
|
||||
if parentgs not in layoutgrids:
|
||||
layoutgrids = make_layoutgrids_gs(layoutgrids, parentgs)
|
||||
subspeclb = layoutgrids[parentgs]
|
||||
# gridspecfromsubplotspec need an outer container:
|
||||
# get a unique representation:
|
||||
rep = (gs, 'top')
|
||||
if rep not in layoutgrids:
|
||||
layoutgrids[rep] = mlayoutgrid.LayoutGrid(
|
||||
parent=subspeclb,
|
||||
name='top',
|
||||
nrows=1, ncols=1,
|
||||
parent_pos=(subplot_spec.rowspan, subplot_spec.colspan))
|
||||
layoutgrids[gs] = mlayoutgrid.LayoutGrid(
|
||||
parent=layoutgrids[rep],
|
||||
name='gridspec',
|
||||
nrows=gs._nrows, ncols=gs._ncols,
|
||||
width_ratios=gs.get_width_ratios(),
|
||||
height_ratios=gs.get_height_ratios())
|
||||
return layoutgrids
|
||||
|
||||
|
||||
def check_no_collapsed_axes(layoutgrids, fig):
|
||||
"""
|
||||
Check that no Axes have collapsed to zero size.
|
||||
"""
|
||||
for sfig in fig.subfigs:
|
||||
ok = check_no_collapsed_axes(layoutgrids, sfig)
|
||||
if not ok:
|
||||
return False
|
||||
for ax in fig.axes:
|
||||
gs = ax.get_gridspec()
|
||||
if gs in layoutgrids: # also implies gs is not None.
|
||||
lg = layoutgrids[gs]
|
||||
for i in range(gs.nrows):
|
||||
for j in range(gs.ncols):
|
||||
bb = lg.get_inner_bbox(i, j)
|
||||
if bb.width <= 0 or bb.height <= 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def compress_fixed_aspect(layoutgrids, fig):
|
||||
gs = None
|
||||
for ax in fig.axes:
|
||||
if ax.get_subplotspec() is None:
|
||||
continue
|
||||
ax.apply_aspect()
|
||||
sub = ax.get_subplotspec()
|
||||
_gs = sub.get_gridspec()
|
||||
if gs is None:
|
||||
gs = _gs
|
||||
extraw = np.zeros(gs.ncols)
|
||||
extrah = np.zeros(gs.nrows)
|
||||
elif _gs != gs:
|
||||
raise ValueError('Cannot do compressed layout if Axes are not'
|
||||
'all from the same gridspec')
|
||||
orig = ax.get_position(original=True)
|
||||
actual = ax.get_position(original=False)
|
||||
dw = orig.width - actual.width
|
||||
if dw > 0:
|
||||
extraw[sub.colspan] = np.maximum(extraw[sub.colspan], dw)
|
||||
dh = orig.height - actual.height
|
||||
if dh > 0:
|
||||
extrah[sub.rowspan] = np.maximum(extrah[sub.rowspan], dh)
|
||||
|
||||
if gs is None:
|
||||
raise ValueError('Cannot do compressed layout if no Axes '
|
||||
'are part of a gridspec.')
|
||||
w = np.sum(extraw) / 2
|
||||
layoutgrids[fig].edit_margin_min('left', w)
|
||||
layoutgrids[fig].edit_margin_min('right', w)
|
||||
|
||||
h = np.sum(extrah) / 2
|
||||
layoutgrids[fig].edit_margin_min('top', h)
|
||||
layoutgrids[fig].edit_margin_min('bottom', h)
|
||||
return layoutgrids
|
||||
|
||||
|
||||
def get_margin_from_padding(obj, *, w_pad=0, h_pad=0,
|
||||
hspace=0, wspace=0):
|
||||
|
||||
ss = obj._subplotspec
|
||||
gs = ss.get_gridspec()
|
||||
|
||||
if hasattr(gs, 'hspace'):
|
||||
_hspace = (gs.hspace if gs.hspace is not None else hspace)
|
||||
_wspace = (gs.wspace if gs.wspace is not None else wspace)
|
||||
else:
|
||||
_hspace = (gs._hspace if gs._hspace is not None else hspace)
|
||||
_wspace = (gs._wspace if gs._wspace is not None else wspace)
|
||||
|
||||
_wspace = _wspace / 2
|
||||
_hspace = _hspace / 2
|
||||
|
||||
nrows, ncols = gs.get_geometry()
|
||||
# there are two margins for each direction. The "cb"
|
||||
# margins are for pads and colorbars, the non-"cb" are
|
||||
# for the Axes decorations (labels etc).
|
||||
margin = {'leftcb': w_pad, 'rightcb': w_pad,
|
||||
'bottomcb': h_pad, 'topcb': h_pad,
|
||||
'left': 0, 'right': 0,
|
||||
'top': 0, 'bottom': 0}
|
||||
if _wspace / ncols > w_pad:
|
||||
if ss.colspan.start > 0:
|
||||
margin['leftcb'] = _wspace / ncols
|
||||
if ss.colspan.stop < ncols:
|
||||
margin['rightcb'] = _wspace / ncols
|
||||
if _hspace / nrows > h_pad:
|
||||
if ss.rowspan.stop < nrows:
|
||||
margin['bottomcb'] = _hspace / nrows
|
||||
if ss.rowspan.start > 0:
|
||||
margin['topcb'] = _hspace / nrows
|
||||
|
||||
return margin
|
||||
|
||||
|
||||
def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0,
|
||||
hspace=0, wspace=0):
|
||||
"""
|
||||
For each Axes, make a margin between the *pos* layoutbox and the
|
||||
*axes* layoutbox be a minimum size that can accommodate the
|
||||
decorations on the axis.
|
||||
|
||||
Then make room for colorbars.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
layoutgrids : dict
|
||||
fig : `~matplotlib.figure.Figure`
|
||||
`.Figure` instance to do the layout in.
|
||||
renderer : `~matplotlib.backend_bases.RendererBase` subclass.
|
||||
The renderer to use.
|
||||
w_pad, h_pad : float, default: 0
|
||||
Width and height padding (in fraction of figure).
|
||||
hspace, wspace : float, default: 0
|
||||
Width and height padding as fraction of figure size divided by
|
||||
number of columns or rows.
|
||||
"""
|
||||
for sfig in fig.subfigs: # recursively make child panel margins
|
||||
ss = sfig._subplotspec
|
||||
gs = ss.get_gridspec()
|
||||
|
||||
make_layout_margins(layoutgrids, sfig, renderer,
|
||||
w_pad=w_pad, h_pad=h_pad,
|
||||
hspace=hspace, wspace=wspace)
|
||||
|
||||
margins = get_margin_from_padding(sfig, w_pad=0, h_pad=0,
|
||||
hspace=hspace, wspace=wspace)
|
||||
layoutgrids[gs].edit_outer_margin_mins(margins, ss)
|
||||
|
||||
for ax in fig._localaxes:
|
||||
if not ax.get_subplotspec() or not ax.get_in_layout():
|
||||
continue
|
||||
|
||||
ss = ax.get_subplotspec()
|
||||
gs = ss.get_gridspec()
|
||||
|
||||
if gs not in layoutgrids:
|
||||
return
|
||||
|
||||
margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad,
|
||||
hspace=hspace, wspace=wspace)
|
||||
pos, bbox = get_pos_and_bbox(ax, renderer)
|
||||
# the margin is the distance between the bounding box of the Axes
|
||||
# and its position (plus the padding from above)
|
||||
margin['left'] += pos.x0 - bbox.x0
|
||||
margin['right'] += bbox.x1 - pos.x1
|
||||
# remember that rows are ordered from top:
|
||||
margin['bottom'] += pos.y0 - bbox.y0
|
||||
margin['top'] += bbox.y1 - pos.y1
|
||||
|
||||
# make margin for colorbars. These margins go in the
|
||||
# padding margin, versus the margin for Axes decorators.
|
||||
for cbax in ax._colorbars:
|
||||
# note pad is a fraction of the parent width...
|
||||
pad = colorbar_get_pad(layoutgrids, cbax)
|
||||
# colorbars can be child of more than one subplot spec:
|
||||
cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax)
|
||||
loc = cbax._colorbar_info['location']
|
||||
cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)
|
||||
if loc == 'right':
|
||||
if cbp_cspan.stop == ss.colspan.stop:
|
||||
# only increase if the colorbar is on the right edge
|
||||
margin['rightcb'] += cbbbox.width + pad
|
||||
elif loc == 'left':
|
||||
if cbp_cspan.start == ss.colspan.start:
|
||||
# only increase if the colorbar is on the left edge
|
||||
margin['leftcb'] += cbbbox.width + pad
|
||||
elif loc == 'top':
|
||||
if cbp_rspan.start == ss.rowspan.start:
|
||||
margin['topcb'] += cbbbox.height + pad
|
||||
else:
|
||||
if cbp_rspan.stop == ss.rowspan.stop:
|
||||
margin['bottomcb'] += cbbbox.height + pad
|
||||
# If the colorbars are wider than the parent box in the
|
||||
# cross direction
|
||||
if loc in ['top', 'bottom']:
|
||||
if (cbp_cspan.start == ss.colspan.start and
|
||||
cbbbox.x0 < bbox.x0):
|
||||
margin['left'] += bbox.x0 - cbbbox.x0
|
||||
if (cbp_cspan.stop == ss.colspan.stop and
|
||||
cbbbox.x1 > bbox.x1):
|
||||
margin['right'] += cbbbox.x1 - bbox.x1
|
||||
# or taller:
|
||||
if loc in ['left', 'right']:
|
||||
if (cbp_rspan.stop == ss.rowspan.stop and
|
||||
cbbbox.y0 < bbox.y0):
|
||||
margin['bottom'] += bbox.y0 - cbbbox.y0
|
||||
if (cbp_rspan.start == ss.rowspan.start and
|
||||
cbbbox.y1 > bbox.y1):
|
||||
margin['top'] += cbbbox.y1 - bbox.y1
|
||||
# pass the new margins down to the layout grid for the solution...
|
||||
layoutgrids[gs].edit_outer_margin_mins(margin, ss)
|
||||
|
||||
# make margins for figure-level legends:
|
||||
for leg in fig.legends:
|
||||
inv_trans_fig = None
|
||||
if leg._outside_loc and leg._bbox_to_anchor is None:
|
||||
if inv_trans_fig is None:
|
||||
inv_trans_fig = fig.transFigure.inverted().transform_bbox
|
||||
bbox = inv_trans_fig(leg.get_tightbbox(renderer))
|
||||
w = bbox.width + 2 * w_pad
|
||||
h = bbox.height + 2 * h_pad
|
||||
legendloc = leg._outside_loc
|
||||
if legendloc == 'lower':
|
||||
layoutgrids[fig].edit_margin_min('bottom', h)
|
||||
elif legendloc == 'upper':
|
||||
layoutgrids[fig].edit_margin_min('top', h)
|
||||
if legendloc == 'right':
|
||||
layoutgrids[fig].edit_margin_min('right', w)
|
||||
elif legendloc == 'left':
|
||||
layoutgrids[fig].edit_margin_min('left', w)
|
||||
|
||||
|
||||
def make_margin_suptitles(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0):
|
||||
# Figure out how large the suptitle is and make the
|
||||
# top level figure margin larger.
|
||||
|
||||
inv_trans_fig = fig.transFigure.inverted().transform_bbox
|
||||
# get the h_pad and w_pad as distances in the local subfigure coordinates:
|
||||
padbox = mtransforms.Bbox([[0, 0], [w_pad, h_pad]])
|
||||
padbox = (fig.transFigure -
|
||||
fig.transSubfigure).transform_bbox(padbox)
|
||||
h_pad_local = padbox.height
|
||||
w_pad_local = padbox.width
|
||||
|
||||
for sfig in fig.subfigs:
|
||||
make_margin_suptitles(layoutgrids, sfig, renderer,
|
||||
w_pad=w_pad, h_pad=h_pad)
|
||||
|
||||
if fig._suptitle is not None and fig._suptitle.get_in_layout():
|
||||
p = fig._suptitle.get_position()
|
||||
if getattr(fig._suptitle, '_autopos', False):
|
||||
fig._suptitle.set_position((p[0], 1 - h_pad_local))
|
||||
bbox = inv_trans_fig(fig._suptitle.get_tightbbox(renderer))
|
||||
layoutgrids[fig].edit_margin_min('top', bbox.height + 2 * h_pad)
|
||||
|
||||
if fig._supxlabel is not None and fig._supxlabel.get_in_layout():
|
||||
p = fig._supxlabel.get_position()
|
||||
if getattr(fig._supxlabel, '_autopos', False):
|
||||
fig._supxlabel.set_position((p[0], h_pad_local))
|
||||
bbox = inv_trans_fig(fig._supxlabel.get_tightbbox(renderer))
|
||||
layoutgrids[fig].edit_margin_min('bottom',
|
||||
bbox.height + 2 * h_pad)
|
||||
|
||||
if fig._supylabel is not None and fig._supylabel.get_in_layout():
|
||||
p = fig._supylabel.get_position()
|
||||
if getattr(fig._supylabel, '_autopos', False):
|
||||
fig._supylabel.set_position((w_pad_local, p[1]))
|
||||
bbox = inv_trans_fig(fig._supylabel.get_tightbbox(renderer))
|
||||
layoutgrids[fig].edit_margin_min('left', bbox.width + 2 * w_pad)
|
||||
|
||||
|
||||
def match_submerged_margins(layoutgrids, fig):
|
||||
"""
|
||||
Make the margins that are submerged inside an Axes the same size.
|
||||
|
||||
This allows Axes that span two columns (or rows) that are offset
|
||||
from one another to have the same size.
|
||||
|
||||
This gives the proper layout for something like::
|
||||
fig = plt.figure(constrained_layout=True)
|
||||
axs = fig.subplot_mosaic("AAAB\nCCDD")
|
||||
|
||||
Without this routine, the Axes D will be wider than C, because the
|
||||
margin width between the two columns in C has no width by default,
|
||||
whereas the margins between the two columns of D are set by the
|
||||
width of the margin between A and B. However, obviously the user would
|
||||
like C and D to be the same size, so we need to add constraints to these
|
||||
"submerged" margins.
|
||||
|
||||
This routine makes all the interior margins the same, and the spacing
|
||||
between the three columns in A and the two column in C are all set to the
|
||||
margins between the two columns of D.
|
||||
|
||||
See test_constrained_layout::test_constrained_layout12 for an example.
|
||||
"""
|
||||
|
||||
axsdone = []
|
||||
for sfig in fig.subfigs:
|
||||
axsdone += match_submerged_margins(layoutgrids, sfig)
|
||||
|
||||
axs = [a for a in fig.get_axes()
|
||||
if (a.get_subplotspec() is not None and a.get_in_layout() and
|
||||
a not in axsdone)]
|
||||
|
||||
for ax1 in axs:
|
||||
ss1 = ax1.get_subplotspec()
|
||||
if ss1.get_gridspec() not in layoutgrids:
|
||||
axs.remove(ax1)
|
||||
continue
|
||||
lg1 = layoutgrids[ss1.get_gridspec()]
|
||||
|
||||
# interior columns:
|
||||
if len(ss1.colspan) > 1:
|
||||
maxsubl = np.max(
|
||||
lg1.margin_vals['left'][ss1.colspan[1:]] +
|
||||
lg1.margin_vals['leftcb'][ss1.colspan[1:]]
|
||||
)
|
||||
maxsubr = np.max(
|
||||
lg1.margin_vals['right'][ss1.colspan[:-1]] +
|
||||
lg1.margin_vals['rightcb'][ss1.colspan[:-1]]
|
||||
)
|
||||
for ax2 in axs:
|
||||
ss2 = ax2.get_subplotspec()
|
||||
lg2 = layoutgrids[ss2.get_gridspec()]
|
||||
if lg2 is not None and len(ss2.colspan) > 1:
|
||||
maxsubl2 = np.max(
|
||||
lg2.margin_vals['left'][ss2.colspan[1:]] +
|
||||
lg2.margin_vals['leftcb'][ss2.colspan[1:]])
|
||||
if maxsubl2 > maxsubl:
|
||||
maxsubl = maxsubl2
|
||||
maxsubr2 = np.max(
|
||||
lg2.margin_vals['right'][ss2.colspan[:-1]] +
|
||||
lg2.margin_vals['rightcb'][ss2.colspan[:-1]])
|
||||
if maxsubr2 > maxsubr:
|
||||
maxsubr = maxsubr2
|
||||
for i in ss1.colspan[1:]:
|
||||
lg1.edit_margin_min('left', maxsubl, cell=i)
|
||||
for i in ss1.colspan[:-1]:
|
||||
lg1.edit_margin_min('right', maxsubr, cell=i)
|
||||
|
||||
# interior rows:
|
||||
if len(ss1.rowspan) > 1:
|
||||
maxsubt = np.max(
|
||||
lg1.margin_vals['top'][ss1.rowspan[1:]] +
|
||||
lg1.margin_vals['topcb'][ss1.rowspan[1:]]
|
||||
)
|
||||
maxsubb = np.max(
|
||||
lg1.margin_vals['bottom'][ss1.rowspan[:-1]] +
|
||||
lg1.margin_vals['bottomcb'][ss1.rowspan[:-1]]
|
||||
)
|
||||
|
||||
for ax2 in axs:
|
||||
ss2 = ax2.get_subplotspec()
|
||||
lg2 = layoutgrids[ss2.get_gridspec()]
|
||||
if lg2 is not None:
|
||||
if len(ss2.rowspan) > 1:
|
||||
maxsubt = np.max([np.max(
|
||||
lg2.margin_vals['top'][ss2.rowspan[1:]] +
|
||||
lg2.margin_vals['topcb'][ss2.rowspan[1:]]
|
||||
), maxsubt])
|
||||
maxsubb = np.max([np.max(
|
||||
lg2.margin_vals['bottom'][ss2.rowspan[:-1]] +
|
||||
lg2.margin_vals['bottomcb'][ss2.rowspan[:-1]]
|
||||
), maxsubb])
|
||||
for i in ss1.rowspan[1:]:
|
||||
lg1.edit_margin_min('top', maxsubt, cell=i)
|
||||
for i in ss1.rowspan[:-1]:
|
||||
lg1.edit_margin_min('bottom', maxsubb, cell=i)
|
||||
|
||||
return axs
|
||||
|
||||
|
||||
def get_cb_parent_spans(cbax):
|
||||
"""
|
||||
Figure out which subplotspecs this colorbar belongs to.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cbax : `~matplotlib.axes.Axes`
|
||||
Axes for the colorbar.
|
||||
"""
|
||||
rowstart = np.inf
|
||||
rowstop = -np.inf
|
||||
colstart = np.inf
|
||||
colstop = -np.inf
|
||||
for parent in cbax._colorbar_info['parents']:
|
||||
ss = parent.get_subplotspec()
|
||||
rowstart = min(ss.rowspan.start, rowstart)
|
||||
rowstop = max(ss.rowspan.stop, rowstop)
|
||||
colstart = min(ss.colspan.start, colstart)
|
||||
colstop = max(ss.colspan.stop, colstop)
|
||||
|
||||
rowspan = range(rowstart, rowstop)
|
||||
colspan = range(colstart, colstop)
|
||||
return rowspan, colspan
|
||||
|
||||
|
||||
def get_pos_and_bbox(ax, renderer):
|
||||
"""
|
||||
Get the position and the bbox for the Axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ax : `~matplotlib.axes.Axes`
|
||||
renderer : `~matplotlib.backend_bases.RendererBase` subclass.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pos : `~matplotlib.transforms.Bbox`
|
||||
Position in figure coordinates.
|
||||
bbox : `~matplotlib.transforms.Bbox`
|
||||
Tight bounding box in figure coordinates.
|
||||
"""
|
||||
fig = ax.get_figure(root=False)
|
||||
pos = ax.get_position(original=True)
|
||||
# pos is in panel co-ords, but we need in figure for the layout
|
||||
pos = pos.transformed(fig.transSubfigure - fig.transFigure)
|
||||
tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
|
||||
if tightbbox is None:
|
||||
bbox = pos
|
||||
else:
|
||||
bbox = tightbbox.transformed(fig.transFigure.inverted())
|
||||
return pos, bbox
|
||||
|
||||
|
||||
def reposition_axes(layoutgrids, fig, renderer, *,
|
||||
w_pad=0, h_pad=0, hspace=0, wspace=0):
|
||||
"""
|
||||
Reposition all the Axes based on the new inner bounding box.
|
||||
"""
|
||||
trans_fig_to_subfig = fig.transFigure - fig.transSubfigure
|
||||
for sfig in fig.subfigs:
|
||||
bbox = layoutgrids[sfig].get_outer_bbox()
|
||||
sfig._redo_transform_rel_fig(
|
||||
bbox=bbox.transformed(trans_fig_to_subfig))
|
||||
reposition_axes(layoutgrids, sfig, renderer,
|
||||
w_pad=w_pad, h_pad=h_pad,
|
||||
wspace=wspace, hspace=hspace)
|
||||
|
||||
for ax in fig._localaxes:
|
||||
if ax.get_subplotspec() is None or not ax.get_in_layout():
|
||||
continue
|
||||
|
||||
# grid bbox is in Figure coordinates, but we specify in panel
|
||||
# coordinates...
|
||||
ss = ax.get_subplotspec()
|
||||
gs = ss.get_gridspec()
|
||||
if gs not in layoutgrids:
|
||||
return
|
||||
|
||||
bbox = layoutgrids[gs].get_inner_bbox(rows=ss.rowspan,
|
||||
cols=ss.colspan)
|
||||
|
||||
# transform from figure to panel for set_position:
|
||||
newbbox = trans_fig_to_subfig.transform_bbox(bbox)
|
||||
ax._set_position(newbbox)
|
||||
|
||||
# move the colorbars:
|
||||
# we need to keep track of oldw and oldh if there is more than
|
||||
# one colorbar:
|
||||
offset = {'left': 0, 'right': 0, 'bottom': 0, 'top': 0}
|
||||
for nn, cbax in enumerate(ax._colorbars[::-1]):
|
||||
if ax == cbax._colorbar_info['parents'][0]:
|
||||
reposition_colorbar(layoutgrids, cbax, renderer,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None):
|
||||
"""
|
||||
Place the colorbar in its new place.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
layoutgrids : dict
|
||||
cbax : `~matplotlib.axes.Axes`
|
||||
Axes for the colorbar.
|
||||
renderer : `~matplotlib.backend_bases.RendererBase` subclass.
|
||||
The renderer to use.
|
||||
offset : array-like
|
||||
Offset the colorbar needs to be pushed to in order to
|
||||
account for multiple colorbars.
|
||||
"""
|
||||
|
||||
parents = cbax._colorbar_info['parents']
|
||||
gs = parents[0].get_gridspec()
|
||||
fig = cbax.get_figure(root=False)
|
||||
trans_fig_to_subfig = fig.transFigure - fig.transSubfigure
|
||||
|
||||
cb_rspans, cb_cspans = get_cb_parent_spans(cbax)
|
||||
bboxparent = layoutgrids[gs].get_bbox_for_cb(rows=cb_rspans,
|
||||
cols=cb_cspans)
|
||||
pb = layoutgrids[gs].get_inner_bbox(rows=cb_rspans, cols=cb_cspans)
|
||||
|
||||
location = cbax._colorbar_info['location']
|
||||
anchor = cbax._colorbar_info['anchor']
|
||||
fraction = cbax._colorbar_info['fraction']
|
||||
aspect = cbax._colorbar_info['aspect']
|
||||
shrink = cbax._colorbar_info['shrink']
|
||||
|
||||
cbpos, cbbbox = get_pos_and_bbox(cbax, renderer)
|
||||
|
||||
# Colorbar gets put at extreme edge of outer bbox of the subplotspec
|
||||
# It needs to be moved in by: 1) a pad 2) its "margin" 3) by
|
||||
# any colorbars already added at this location:
|
||||
cbpad = colorbar_get_pad(layoutgrids, cbax)
|
||||
if location in ('left', 'right'):
|
||||
# fraction and shrink are fractions of parent
|
||||
pbcb = pb.shrunk(fraction, shrink).anchored(anchor, pb)
|
||||
# The colorbar is at the left side of the parent. Need
|
||||
# to translate to right (or left)
|
||||
if location == 'right':
|
||||
lmargin = cbpos.x0 - cbbbox.x0
|
||||
dx = bboxparent.x1 - pbcb.x0 + offset['right']
|
||||
dx += cbpad + lmargin
|
||||
offset['right'] += cbbbox.width + cbpad
|
||||
pbcb = pbcb.translated(dx, 0)
|
||||
else:
|
||||
lmargin = cbpos.x0 - cbbbox.x0
|
||||
dx = bboxparent.x0 - pbcb.x0 # edge of parent
|
||||
dx += -cbbbox.width - cbpad + lmargin - offset['left']
|
||||
offset['left'] += cbbbox.width + cbpad
|
||||
pbcb = pbcb.translated(dx, 0)
|
||||
else: # horizontal axes:
|
||||
pbcb = pb.shrunk(shrink, fraction).anchored(anchor, pb)
|
||||
if location == 'top':
|
||||
bmargin = cbpos.y0 - cbbbox.y0
|
||||
dy = bboxparent.y1 - pbcb.y0 + offset['top']
|
||||
dy += cbpad + bmargin
|
||||
offset['top'] += cbbbox.height + cbpad
|
||||
pbcb = pbcb.translated(0, dy)
|
||||
else:
|
||||
bmargin = cbpos.y0 - cbbbox.y0
|
||||
dy = bboxparent.y0 - pbcb.y0
|
||||
dy += -cbbbox.height - cbpad + bmargin - offset['bottom']
|
||||
offset['bottom'] += cbbbox.height + cbpad
|
||||
pbcb = pbcb.translated(0, dy)
|
||||
|
||||
pbcb = trans_fig_to_subfig.transform_bbox(pbcb)
|
||||
cbax.set_transform(fig.transSubfigure)
|
||||
cbax._set_position(pbcb)
|
||||
cbax.set_anchor(anchor)
|
||||
if location in ['bottom', 'top']:
|
||||
aspect = 1 / aspect
|
||||
cbax.set_box_aspect(aspect)
|
||||
cbax.set_aspect('auto')
|
||||
return offset
|
||||
|
||||
|
||||
def reset_margins(layoutgrids, fig):
|
||||
"""
|
||||
Reset the margins in the layoutboxes of *fig*.
|
||||
|
||||
Margins are usually set as a minimum, so if the figure gets smaller
|
||||
the minimum needs to be zero in order for it to grow again.
|
||||
"""
|
||||
for sfig in fig.subfigs:
|
||||
reset_margins(layoutgrids, sfig)
|
||||
for ax in fig.axes:
|
||||
if ax.get_in_layout():
|
||||
gs = ax.get_gridspec()
|
||||
if gs in layoutgrids: # also implies gs is not None.
|
||||
layoutgrids[gs].reset_margins()
|
||||
layoutgrids[fig].reset_margins()
|
||||
|
||||
|
||||
def colorbar_get_pad(layoutgrids, cax):
|
||||
parents = cax._colorbar_info['parents']
|
||||
gs = parents[0].get_gridspec()
|
||||
|
||||
cb_rspans, cb_cspans = get_cb_parent_spans(cax)
|
||||
bboxouter = layoutgrids[gs].get_inner_bbox(rows=cb_rspans, cols=cb_cspans)
|
||||
|
||||
if cax._colorbar_info['location'] in ['right', 'left']:
|
||||
size = bboxouter.width
|
||||
else:
|
||||
size = bboxouter.height
|
||||
|
||||
return cax._colorbar_info['pad'] * size
|
||||
141
venv/lib/python3.13/site-packages/matplotlib/_docstring.py
Normal file
141
venv/lib/python3.13/site-packages/matplotlib/_docstring.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import inspect
|
||||
|
||||
from . import _api
|
||||
|
||||
|
||||
def kwarg_doc(text):
|
||||
"""
|
||||
Decorator for defining the kwdoc documentation of artist properties.
|
||||
|
||||
This decorator can be applied to artist property setter methods.
|
||||
The given text is stored in a private attribute ``_kwarg_doc`` on
|
||||
the method. It is used to overwrite auto-generated documentation
|
||||
in the *kwdoc list* for artists. The kwdoc list is used to document
|
||||
``**kwargs`` when they are properties of an artist. See e.g. the
|
||||
``**kwargs`` section in `.Axes.text`.
|
||||
|
||||
The text should contain the supported types, as well as the default
|
||||
value if applicable, e.g.:
|
||||
|
||||
@_docstring.kwarg_doc("bool, default: :rc:`text.usetex`")
|
||||
def set_usetex(self, usetex):
|
||||
|
||||
See Also
|
||||
--------
|
||||
matplotlib.artist.kwdoc
|
||||
|
||||
"""
|
||||
def decorator(func):
|
||||
func._kwarg_doc = text
|
||||
return func
|
||||
return decorator
|
||||
|
||||
|
||||
class Substitution:
|
||||
"""
|
||||
A decorator that performs %-substitution on an object's docstring.
|
||||
|
||||
This decorator should be robust even if ``obj.__doc__`` is None (for
|
||||
example, if -OO was passed to the interpreter).
|
||||
|
||||
Usage: construct a docstring.Substitution with a sequence or dictionary
|
||||
suitable for performing substitution; then decorate a suitable function
|
||||
with the constructed object, e.g.::
|
||||
|
||||
sub_author_name = Substitution(author='Jason')
|
||||
|
||||
@sub_author_name
|
||||
def some_function(x):
|
||||
"%(author)s wrote this function"
|
||||
|
||||
# note that some_function.__doc__ is now "Jason wrote this function"
|
||||
|
||||
One can also use positional arguments::
|
||||
|
||||
sub_first_last_names = Substitution('Edgar Allen', 'Poe')
|
||||
|
||||
@sub_first_last_names
|
||||
def some_function(x):
|
||||
"%s %s wrote the Raven"
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
if args and kwargs:
|
||||
raise TypeError("Only positional or keyword args are allowed")
|
||||
self.params = args or kwargs
|
||||
|
||||
def __call__(self, func):
|
||||
if func.__doc__:
|
||||
func.__doc__ = inspect.cleandoc(func.__doc__) % self.params
|
||||
return func
|
||||
|
||||
|
||||
class _ArtistKwdocLoader(dict):
|
||||
def __missing__(self, key):
|
||||
if not key.endswith(":kwdoc"):
|
||||
raise KeyError(key)
|
||||
name = key[:-len(":kwdoc")]
|
||||
from matplotlib.artist import Artist, kwdoc
|
||||
try:
|
||||
cls, = (cls for cls in _api.recursive_subclasses(Artist)
|
||||
if cls.__name__ == name)
|
||||
except ValueError as e:
|
||||
raise KeyError(key) from e
|
||||
return self.setdefault(key, kwdoc(cls))
|
||||
|
||||
|
||||
class _ArtistPropertiesSubstitution:
|
||||
"""
|
||||
A class to substitute formatted placeholders in docstrings.
|
||||
|
||||
This is realized in a single instance ``_docstring.interpd``.
|
||||
|
||||
Use `~._ArtistPropertiesSubstition.register` to define placeholders and
|
||||
their substitution, e.g. ``_docstring.interpd.register(name="some value")``.
|
||||
|
||||
Use this as a decorator to apply the substitution::
|
||||
|
||||
@_docstring.interpd
|
||||
def some_func():
|
||||
'''Replace %(name)s.'''
|
||||
|
||||
Decorating a class triggers substitution both on the class docstring and
|
||||
on the class' ``__init__`` docstring (which is a commonly required
|
||||
pattern for Artist subclasses).
|
||||
|
||||
Substitutions of the form ``%(classname:kwdoc)s`` (ending with the
|
||||
literal ":kwdoc" suffix) trigger lookup of an Artist subclass with the
|
||||
given *classname*, and are substituted with the `.kwdoc` of that class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.params = _ArtistKwdocLoader()
|
||||
|
||||
def register(self, **kwargs):
|
||||
"""
|
||||
Register substitutions.
|
||||
|
||||
``_docstring.interpd.register(name="some value")`` makes "name" available
|
||||
as a named parameter that will be replaced by "some value".
|
||||
"""
|
||||
self.params.update(**kwargs)
|
||||
|
||||
def __call__(self, obj):
|
||||
if obj.__doc__:
|
||||
obj.__doc__ = inspect.cleandoc(obj.__doc__) % self.params
|
||||
if isinstance(obj, type) and obj.__init__ != object.__init__:
|
||||
self(obj.__init__)
|
||||
return obj
|
||||
|
||||
|
||||
def copy(source):
|
||||
"""Copy a docstring from another source function (if present)."""
|
||||
def do_copy(target):
|
||||
if source.__doc__:
|
||||
target.__doc__ = source.__doc__
|
||||
return target
|
||||
return do_copy
|
||||
|
||||
|
||||
# Create a decorator that will house the various docstring snippets reused
|
||||
# throughout Matplotlib.
|
||||
interpd = _ArtistPropertiesSubstitution()
|
||||
34
venv/lib/python3.13/site-packages/matplotlib/_docstring.pyi
Normal file
34
venv/lib/python3.13/site-packages/matplotlib/_docstring.pyi
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
from collections.abc import Callable
|
||||
from typing import Any, TypeVar, overload
|
||||
|
||||
|
||||
_T = TypeVar('_T')
|
||||
|
||||
|
||||
def kwarg_doc(text: str) -> Callable[[_T], _T]: ...
|
||||
|
||||
|
||||
class Substitution:
|
||||
@overload
|
||||
def __init__(self, *args: str): ...
|
||||
@overload
|
||||
def __init__(self, **kwargs: str): ...
|
||||
def __call__(self, func: _T) -> _T: ...
|
||||
def update(self, *args, **kwargs): ... # type: ignore[no-untyped-def]
|
||||
|
||||
|
||||
class _ArtistKwdocLoader(dict[str, str]):
|
||||
def __missing__(self, key: str) -> str: ...
|
||||
|
||||
|
||||
class _ArtistPropertiesSubstitution:
|
||||
def __init__(self) -> None: ...
|
||||
def register(self, **kwargs) -> None: ...
|
||||
def __call__(self, obj: _T) -> _T: ...
|
||||
|
||||
|
||||
def copy(source: Any) -> Callable[[_T], _T]: ...
|
||||
|
||||
|
||||
dedent_interpd: _ArtistPropertiesSubstitution
|
||||
interpd: _ArtistPropertiesSubstitution
|
||||
177
venv/lib/python3.13/site-packages/matplotlib/_enums.py
Normal file
177
venv/lib/python3.13/site-packages/matplotlib/_enums.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
"""
|
||||
Enums representing sets of strings that Matplotlib uses as input parameters.
|
||||
|
||||
Matplotlib often uses simple data types like strings or tuples to define a
|
||||
concept; e.g. the line capstyle can be specified as one of 'butt', 'round',
|
||||
or 'projecting'. The classes in this module are used internally and serve to
|
||||
document these concepts formally.
|
||||
|
||||
As an end-user you will not use these classes directly, but only the values
|
||||
they define.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from matplotlib import _docstring
|
||||
|
||||
|
||||
class JoinStyle(str, Enum):
|
||||
"""
|
||||
Define how the connection between two line segments is drawn.
|
||||
|
||||
For a visual impression of each *JoinStyle*, `view these docs online
|
||||
<JoinStyle>`, or run `JoinStyle.demo`.
|
||||
|
||||
Lines in Matplotlib are typically defined by a 1D `~.path.Path` and a
|
||||
finite ``linewidth``, where the underlying 1D `~.path.Path` represents the
|
||||
center of the stroked line.
|
||||
|
||||
By default, `~.backend_bases.GraphicsContextBase` defines the boundaries of
|
||||
a stroked line to simply be every point within some radius,
|
||||
``linewidth/2``, away from any point of the center line. However, this
|
||||
results in corners appearing "rounded", which may not be the desired
|
||||
behavior if you are drawing, for example, a polygon or pointed star.
|
||||
|
||||
**Supported values:**
|
||||
|
||||
.. rst-class:: value-list
|
||||
|
||||
'miter'
|
||||
the "arrow-tip" style. Each boundary of the filled-in area will
|
||||
extend in a straight line parallel to the tangent vector of the
|
||||
centerline at the point it meets the corner, until they meet in a
|
||||
sharp point.
|
||||
'round'
|
||||
stokes every point within a radius of ``linewidth/2`` of the center
|
||||
lines.
|
||||
'bevel'
|
||||
the "squared-off" style. It can be thought of as a rounded corner
|
||||
where the "circular" part of the corner has been cut off.
|
||||
|
||||
.. note::
|
||||
|
||||
Very long miter tips are cut off (to form a *bevel*) after a
|
||||
backend-dependent limit called the "miter limit", which specifies the
|
||||
maximum allowed ratio of miter length to line width. For example, the
|
||||
PDF backend uses the default value of 10 specified by the PDF standard,
|
||||
while the SVG backend does not even specify the miter limit, resulting
|
||||
in a default value of 4 per the SVG specification. Matplotlib does not
|
||||
currently allow the user to adjust this parameter.
|
||||
|
||||
A more detailed description of the effect of a miter limit can be found
|
||||
in the `Mozilla Developer Docs
|
||||
<https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit>`_
|
||||
|
||||
.. plot::
|
||||
:alt: Demo of possible JoinStyle's
|
||||
|
||||
from matplotlib._enums import JoinStyle
|
||||
JoinStyle.demo()
|
||||
|
||||
"""
|
||||
|
||||
miter = "miter"
|
||||
round = "round"
|
||||
bevel = "bevel"
|
||||
|
||||
@staticmethod
|
||||
def demo():
|
||||
"""Demonstrate how each JoinStyle looks for various join angles."""
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
def plot_angle(ax, x, y, angle, style):
|
||||
phi = np.radians(angle)
|
||||
xx = [x + .5, x, x + .5*np.cos(phi)]
|
||||
yy = [y, y, y + .5*np.sin(phi)]
|
||||
ax.plot(xx, yy, lw=12, color='tab:blue', solid_joinstyle=style)
|
||||
ax.plot(xx, yy, lw=1, color='black')
|
||||
ax.plot(xx[1], yy[1], 'o', color='tab:red', markersize=3)
|
||||
|
||||
fig, ax = plt.subplots(figsize=(5, 4), constrained_layout=True)
|
||||
ax.set_title('Join style')
|
||||
for x, style in enumerate(['miter', 'round', 'bevel']):
|
||||
ax.text(x, 5, style)
|
||||
for y, angle in enumerate([20, 45, 60, 90, 120]):
|
||||
plot_angle(ax, x, y, angle, style)
|
||||
if x == 0:
|
||||
ax.text(-1.3, y, f'{angle} degrees')
|
||||
ax.set_xlim(-1.5, 2.75)
|
||||
ax.set_ylim(-.5, 5.5)
|
||||
ax.set_axis_off()
|
||||
fig.show()
|
||||
|
||||
|
||||
JoinStyle.input_description = "{" \
|
||||
+ ", ".join([f"'{js.name}'" for js in JoinStyle]) \
|
||||
+ "}"
|
||||
|
||||
|
||||
class CapStyle(str, Enum):
|
||||
r"""
|
||||
Define how the two endpoints (caps) of an unclosed line are drawn.
|
||||
|
||||
How to draw the start and end points of lines that represent a closed curve
|
||||
(i.e. that end in a `~.path.Path.CLOSEPOLY`) is controlled by the line's
|
||||
`JoinStyle`. For all other lines, how the start and end points are drawn is
|
||||
controlled by the *CapStyle*.
|
||||
|
||||
For a visual impression of each *CapStyle*, `view these docs online
|
||||
<CapStyle>` or run `CapStyle.demo`.
|
||||
|
||||
By default, `~.backend_bases.GraphicsContextBase` draws a stroked line as
|
||||
squared off at its endpoints.
|
||||
|
||||
**Supported values:**
|
||||
|
||||
.. rst-class:: value-list
|
||||
|
||||
'butt'
|
||||
the line is squared off at its endpoint.
|
||||
'projecting'
|
||||
the line is squared off as in *butt*, but the filled in area
|
||||
extends beyond the endpoint a distance of ``linewidth/2``.
|
||||
'round'
|
||||
like *butt*, but a semicircular cap is added to the end of the
|
||||
line, of radius ``linewidth/2``.
|
||||
|
||||
.. plot::
|
||||
:alt: Demo of possible CapStyle's
|
||||
|
||||
from matplotlib._enums import CapStyle
|
||||
CapStyle.demo()
|
||||
|
||||
"""
|
||||
butt = "butt"
|
||||
projecting = "projecting"
|
||||
round = "round"
|
||||
|
||||
@staticmethod
|
||||
def demo():
|
||||
"""Demonstrate how each CapStyle looks for a thick line segment."""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
fig = plt.figure(figsize=(4, 1.2))
|
||||
ax = fig.add_axes([0, 0, 1, 0.8])
|
||||
ax.set_title('Cap style')
|
||||
|
||||
for x, style in enumerate(['butt', 'round', 'projecting']):
|
||||
ax.text(x+0.25, 0.85, style, ha='center')
|
||||
xx = [x, x+0.5]
|
||||
yy = [0, 0]
|
||||
ax.plot(xx, yy, lw=12, color='tab:blue', solid_capstyle=style)
|
||||
ax.plot(xx, yy, lw=1, color='black')
|
||||
ax.plot(xx, yy, 'o', color='tab:red', markersize=3)
|
||||
|
||||
ax.set_ylim(-.5, 1.5)
|
||||
ax.set_axis_off()
|
||||
fig.show()
|
||||
|
||||
|
||||
CapStyle.input_description = "{" \
|
||||
+ ", ".join([f"'{cs.name}'" for cs in CapStyle]) \
|
||||
+ "}"
|
||||
|
||||
_docstring.interpd.register(
|
||||
JoinStyle=JoinStyle.input_description,
|
||||
CapStyle=CapStyle.input_description,
|
||||
)
|
||||
19
venv/lib/python3.13/site-packages/matplotlib/_enums.pyi
Normal file
19
venv/lib/python3.13/site-packages/matplotlib/_enums.pyi
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from typing import cast
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class JoinStyle(str, Enum):
|
||||
miter = "miter"
|
||||
round = "round"
|
||||
bevel = "bevel"
|
||||
@staticmethod
|
||||
def demo() -> None: ...
|
||||
|
||||
|
||||
class CapStyle(str, Enum):
|
||||
butt = "butt"
|
||||
projecting = "projecting"
|
||||
round = "round"
|
||||
|
||||
@staticmethod
|
||||
def demo() -> None: ...
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue