|
from collections.abc import Iterable, Sequence |
|
from contextlib import ExitStack |
|
import functools |
|
import inspect |
|
import logging |
|
from numbers import Real |
|
from operator import attrgetter |
|
import types |
|
|
|
import numpy as np |
|
|
|
import matplotlib as mpl |
|
from matplotlib import _api, cbook, _docstring, offsetbox |
|
import matplotlib.artist as martist |
|
import matplotlib.axis as maxis |
|
from matplotlib.cbook import _OrderedSet, _check_1d, index_of |
|
import matplotlib.collections as mcoll |
|
import matplotlib.colors as mcolors |
|
import matplotlib.font_manager as font_manager |
|
from matplotlib.gridspec import SubplotSpec |
|
import matplotlib.image as mimage |
|
import matplotlib.lines as mlines |
|
import matplotlib.patches as mpatches |
|
from matplotlib.rcsetup import cycler, validate_axisbelow |
|
import matplotlib.spines as mspines |
|
import matplotlib.table as mtable |
|
import matplotlib.text as mtext |
|
import matplotlib.ticker as mticker |
|
import matplotlib.transforms as mtransforms |
|
|
|
_log = logging.getLogger(__name__) |
|
|
|
|
|
class _axis_method_wrapper: |
|
""" |
|
Helper to generate Axes methods wrapping Axis methods. |
|
|
|
After :: |
|
|
|
get_foo = _axis_method_wrapper("xaxis", "get_bar") |
|
|
|
(in the body of a class) ``get_foo`` is a method that forwards it arguments |
|
to the ``get_bar`` method of the ``xaxis`` attribute, and gets its |
|
signature and docstring from ``Axis.get_bar``. |
|
|
|
The docstring of ``get_foo`` is built by replacing "this Axis" by "the |
|
{attr_name}" (i.e., "the xaxis", "the yaxis") in the wrapped method's |
|
dedented docstring; additional replacements can be given in *doc_sub*. |
|
""" |
|
|
|
def __init__(self, attr_name, method_name, *, doc_sub=None): |
|
self.attr_name = attr_name |
|
self.method_name = method_name |
|
|
|
|
|
doc = inspect.getdoc(getattr(maxis.Axis, method_name)) |
|
self._missing_subs = [] |
|
if doc: |
|
doc_sub = {"this Axis": f"the {self.attr_name}", **(doc_sub or {})} |
|
for k, v in doc_sub.items(): |
|
if k not in doc: |
|
self._missing_subs.append(k) |
|
doc = doc.replace(k, v) |
|
self.__doc__ = doc |
|
|
|
def __set_name__(self, owner, name): |
|
|
|
|
|
|
|
get_method = attrgetter(f"{self.attr_name}.{self.method_name}") |
|
|
|
def wrapper(self, *args, **kwargs): |
|
return get_method(self)(*args, **kwargs) |
|
|
|
wrapper.__module__ = owner.__module__ |
|
wrapper.__name__ = name |
|
wrapper.__qualname__ = f"{owner.__qualname__}.{name}" |
|
wrapper.__doc__ = self.__doc__ |
|
|
|
|
|
|
|
wrapper.__signature__ = inspect.signature( |
|
getattr(maxis.Axis, self.method_name)) |
|
|
|
if self._missing_subs: |
|
raise ValueError( |
|
"The definition of {} expected that the docstring of Axis.{} " |
|
"contains {!r} as substrings".format( |
|
wrapper.__qualname__, self.method_name, |
|
", ".join(map(repr, self._missing_subs)))) |
|
|
|
setattr(owner, name, wrapper) |
|
|
|
|
|
class _TransformedBoundsLocator: |
|
""" |
|
Axes locator for `.Axes.inset_axes` and similarly positioned Axes. |
|
|
|
The locator is a callable object used in `.Axes.set_aspect` to compute the |
|
Axes location depending on the renderer. |
|
""" |
|
|
|
def __init__(self, bounds, transform): |
|
""" |
|
*bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together |
|
specify the position of the inset Axes. |
|
""" |
|
self._bounds = bounds |
|
self._transform = transform |
|
|
|
def __call__(self, ax, renderer): |
|
|
|
|
|
|
|
return mtransforms.TransformedBbox( |
|
mtransforms.Bbox.from_bounds(*self._bounds), |
|
self._transform - ax.figure.transSubfigure) |
|
|
|
|
|
def _process_plot_format(fmt, *, ambiguous_fmt_datakey=False): |
|
""" |
|
Convert a MATLAB style color/line style format string to a (*linestyle*, |
|
*marker*, *color*) tuple. |
|
|
|
Example format strings include: |
|
|
|
* 'ko': black circles |
|
* '.b': blue dots |
|
* 'r--': red dashed lines |
|
* 'C2--': the third color in the color cycle, dashed lines |
|
|
|
The format is absolute in the sense that if a linestyle or marker is not |
|
defined in *fmt*, there is no line or marker. This is expressed by |
|
returning 'None' for the respective quantity. |
|
|
|
See Also |
|
-------- |
|
matplotlib.Line2D.lineStyles, matplotlib.colors.cnames |
|
All possible styles and color format strings. |
|
""" |
|
|
|
linestyle = None |
|
marker = None |
|
color = None |
|
|
|
|
|
try: |
|
color = mcolors.to_rgba(fmt) |
|
|
|
|
|
try: |
|
fmtint = str(int(fmt)) |
|
except ValueError: |
|
return linestyle, marker, color |
|
else: |
|
if fmt != fmtint: |
|
|
|
return linestyle, marker, color |
|
else: |
|
|
|
color = None |
|
except ValueError: |
|
pass |
|
|
|
errfmt = ("{!r} is neither a data key nor a valid format string ({})" |
|
if ambiguous_fmt_datakey else |
|
"{!r} is not a valid format string ({})") |
|
|
|
i = 0 |
|
while i < len(fmt): |
|
c = fmt[i] |
|
if fmt[i:i+2] in mlines.lineStyles: |
|
if linestyle is not None: |
|
raise ValueError(errfmt.format(fmt, "two linestyle symbols")) |
|
linestyle = fmt[i:i+2] |
|
i += 2 |
|
elif c in mlines.lineStyles: |
|
if linestyle is not None: |
|
raise ValueError(errfmt.format(fmt, "two linestyle symbols")) |
|
linestyle = c |
|
i += 1 |
|
elif c in mlines.lineMarkers: |
|
if marker is not None: |
|
raise ValueError(errfmt.format(fmt, "two marker symbols")) |
|
marker = c |
|
i += 1 |
|
elif c in mcolors.get_named_colors_mapping(): |
|
if color is not None: |
|
raise ValueError(errfmt.format(fmt, "two color symbols")) |
|
color = c |
|
i += 1 |
|
elif c == 'C' and i < len(fmt) - 1: |
|
color_cycle_number = int(fmt[i + 1]) |
|
color = mcolors.to_rgba(f"C{color_cycle_number}") |
|
i += 2 |
|
else: |
|
raise ValueError( |
|
errfmt.format(fmt, f"unrecognized character {c!r}")) |
|
|
|
if linestyle is None and marker is None: |
|
linestyle = mpl.rcParams['lines.linestyle'] |
|
if linestyle is None: |
|
linestyle = 'None' |
|
if marker is None: |
|
marker = 'None' |
|
|
|
return linestyle, marker, color |
|
|
|
|
|
class _process_plot_var_args: |
|
""" |
|
Process variable length arguments to `~.Axes.plot`, to support :: |
|
|
|
plot(t, s) |
|
plot(t1, s1, t2, s2) |
|
plot(t1, s1, 'ko', t2, s2) |
|
plot(t1, s1, 'ko', t2, s2, 'r--', t3, e3) |
|
|
|
an arbitrary number of *x*, *y*, *fmt* are allowed |
|
""" |
|
|
|
def __init__(self, command='plot'): |
|
self.command = command |
|
self.set_prop_cycle(None) |
|
|
|
def set_prop_cycle(self, cycler): |
|
if cycler is None: |
|
cycler = mpl.rcParams['axes.prop_cycle'] |
|
self._idx = 0 |
|
self._cycler_items = [*cycler] |
|
self._prop_keys = cycler.keys |
|
|
|
def __call__(self, axes, *args, data=None, **kwargs): |
|
axes._process_unit_info(kwargs=kwargs) |
|
|
|
for pos_only in "xy": |
|
if pos_only in kwargs: |
|
raise _api.kwarg_error(self.command, pos_only) |
|
|
|
if not args: |
|
return |
|
|
|
if data is None: |
|
args = [cbook.sanitize_sequence(a) for a in args] |
|
else: |
|
replaced = [mpl._replacer(data, arg) for arg in args] |
|
if len(args) == 1: |
|
label_namer_idx = 0 |
|
elif len(args) == 2: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try: |
|
_process_plot_format(args[1]) |
|
except ValueError: |
|
label_namer_idx = 1 |
|
else: |
|
if replaced[1] is not args[1]: |
|
_api.warn_external( |
|
f"Second argument {args[1]!r} is ambiguous: could " |
|
f"be a format string but is in 'data'; using as " |
|
f"data. If it was intended as data, set the " |
|
f"format string to an empty string to suppress " |
|
f"this warning. If it was intended as a format " |
|
f"string, explicitly pass the x-values as well. " |
|
f"Alternatively, rename the entry in 'data'.", |
|
RuntimeWarning) |
|
label_namer_idx = 1 |
|
else: |
|
label_namer_idx = 0 |
|
elif len(args) == 3: |
|
label_namer_idx = 1 |
|
else: |
|
raise ValueError( |
|
"Using arbitrary long args with data is not supported due " |
|
"to ambiguity of arguments; use multiple plotting calls " |
|
"instead") |
|
if kwargs.get("label") is None: |
|
kwargs["label"] = mpl._label_from_arg( |
|
replaced[label_namer_idx], args[label_namer_idx]) |
|
args = replaced |
|
ambiguous_fmt_datakey = data is not None and len(args) == 2 |
|
|
|
if len(args) >= 4 and not cbook.is_scalar_or_string( |
|
kwargs.get("label")): |
|
raise ValueError("plot() with multiple groups of data (i.e., " |
|
"pairs of x and y) does not support multiple " |
|
"labels") |
|
|
|
|
|
|
|
|
|
while args: |
|
this, args = args[:2], args[2:] |
|
if args and isinstance(args[0], str): |
|
this += args[0], |
|
args = args[1:] |
|
yield from self._plot_args( |
|
axes, this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey) |
|
|
|
def get_next_color(self): |
|
"""Return the next color in the cycle.""" |
|
if 'color' not in self._prop_keys: |
|
return 'k' |
|
c = self._cycler_items[self._idx]['color'] |
|
self._idx = (self._idx + 1) % len(self._cycler_items) |
|
return c |
|
|
|
def _getdefaults(self, ignore, kw): |
|
""" |
|
If some keys in the property cycle (excluding those in the set |
|
*ignore*) are absent or set to None in the dict *kw*, return a copy |
|
of the next entry in the property cycle, excluding keys in *ignore*. |
|
Otherwise, don't advance the property cycle, and return an empty dict. |
|
""" |
|
prop_keys = self._prop_keys - ignore |
|
if any(kw.get(k, None) is None for k in prop_keys): |
|
|
|
|
|
default_dict = self._cycler_items[self._idx].copy() |
|
self._idx = (self._idx + 1) % len(self._cycler_items) |
|
for p in ignore: |
|
default_dict.pop(p, None) |
|
else: |
|
default_dict = {} |
|
return default_dict |
|
|
|
def _setdefaults(self, defaults, kw): |
|
""" |
|
Add to the dict *kw* the entries in the dict *default* that are absent |
|
or set to None in *kw*. |
|
""" |
|
for k in defaults: |
|
if kw.get(k, None) is None: |
|
kw[k] = defaults[k] |
|
|
|
def _makeline(self, axes, x, y, kw, kwargs): |
|
kw = {**kw, **kwargs} |
|
default_dict = self._getdefaults(set(), kw) |
|
self._setdefaults(default_dict, kw) |
|
seg = mlines.Line2D(x, y, **kw) |
|
return seg, kw |
|
|
|
def _makefill(self, axes, x, y, kw, kwargs): |
|
|
|
x = axes.convert_xunits(x) |
|
y = axes.convert_yunits(y) |
|
|
|
kw = kw.copy() |
|
kwargs = kwargs.copy() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ignores = {'marker', 'markersize', 'markeredgecolor', |
|
'markerfacecolor', 'markeredgewidth'} |
|
|
|
for k, v in kwargs.items(): |
|
if v is not None: |
|
ignores.add(k) |
|
|
|
|
|
|
|
|
|
|
|
default_dict = self._getdefaults(ignores, kw) |
|
self._setdefaults(default_dict, kw) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
facecolor = kw.get('color', None) |
|
|
|
|
|
default_dict.pop('color', None) |
|
|
|
|
|
|
|
self._setdefaults(default_dict, kwargs) |
|
|
|
seg = mpatches.Polygon(np.column_stack((x, y)), |
|
facecolor=facecolor, |
|
fill=kwargs.get('fill', True), |
|
closed=kw['closed']) |
|
seg.set(**kwargs) |
|
return seg, kwargs |
|
|
|
def _plot_args(self, axes, tup, kwargs, *, |
|
return_kwargs=False, ambiguous_fmt_datakey=False): |
|
""" |
|
Process the arguments of ``plot([x], y, [fmt], **kwargs)`` calls. |
|
|
|
This processes a single set of ([x], y, [fmt]) parameters; i.e. for |
|
``plot(x, y, x2, y2)`` it will be called twice. Once for (x, y) and |
|
once for (x2, y2). |
|
|
|
x and y may be 2D and thus can still represent multiple datasets. |
|
|
|
For multiple datasets, if the keyword argument *label* is a list, this |
|
will unpack the list and assign the individual labels to the datasets. |
|
|
|
Parameters |
|
---------- |
|
tup : tuple |
|
A tuple of the positional parameters. This can be one of |
|
|
|
- (y,) |
|
- (x, y) |
|
- (y, fmt) |
|
- (x, y, fmt) |
|
|
|
kwargs : dict |
|
The keyword arguments passed to ``plot()``. |
|
|
|
return_kwargs : bool |
|
Whether to also return the effective keyword arguments after label |
|
unpacking as well. |
|
|
|
ambiguous_fmt_datakey : bool |
|
Whether the format string in *tup* could also have been a |
|
misspelled data key. |
|
|
|
Returns |
|
------- |
|
result |
|
If *return_kwargs* is false, a list of Artists representing the |
|
dataset(s). |
|
If *return_kwargs* is true, a list of (Artist, effective_kwargs) |
|
representing the dataset(s). See *return_kwargs*. |
|
The Artist is either `.Line2D` (if called from ``plot()``) or |
|
`.Polygon` otherwise. |
|
""" |
|
if len(tup) > 1 and isinstance(tup[-1], str): |
|
|
|
*xy, fmt = tup |
|
linestyle, marker, color = _process_plot_format( |
|
fmt, ambiguous_fmt_datakey=ambiguous_fmt_datakey) |
|
elif len(tup) == 3: |
|
raise ValueError('third arg must be a format string') |
|
else: |
|
xy = tup |
|
linestyle, marker, color = None, None, None |
|
|
|
|
|
|
|
if any(v is None for v in tup): |
|
raise ValueError("x, y, and format string must not be None") |
|
|
|
kw = {} |
|
for prop_name, val in zip(('linestyle', 'marker', 'color'), |
|
(linestyle, marker, color)): |
|
if val is not None: |
|
|
|
if (fmt.lower() != 'none' |
|
and prop_name in kwargs |
|
and val != 'None'): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_api.warn_external( |
|
f"{prop_name} is redundantly defined by the " |
|
f"'{prop_name}' keyword argument and the fmt string " |
|
f'"{fmt}" (-> {prop_name}={val!r}). The keyword ' |
|
f"argument will take precedence.") |
|
kw[prop_name] = val |
|
|
|
if len(xy) == 2: |
|
x = _check_1d(xy[0]) |
|
y = _check_1d(xy[1]) |
|
else: |
|
x, y = index_of(xy[-1]) |
|
|
|
if axes.xaxis is not None: |
|
axes.xaxis.update_units(x) |
|
if axes.yaxis is not None: |
|
axes.yaxis.update_units(y) |
|
|
|
if x.shape[0] != y.shape[0]: |
|
raise ValueError(f"x and y must have same first dimension, but " |
|
f"have shapes {x.shape} and {y.shape}") |
|
if x.ndim > 2 or y.ndim > 2: |
|
raise ValueError(f"x and y can be no greater than 2D, but have " |
|
f"shapes {x.shape} and {y.shape}") |
|
if x.ndim == 1: |
|
x = x[:, np.newaxis] |
|
if y.ndim == 1: |
|
y = y[:, np.newaxis] |
|
|
|
if self.command == 'plot': |
|
make_artist = self._makeline |
|
else: |
|
kw['closed'] = kwargs.get('closed', True) |
|
make_artist = self._makefill |
|
|
|
ncx, ncy = x.shape[1], y.shape[1] |
|
if ncx > 1 and ncy > 1 and ncx != ncy: |
|
raise ValueError(f"x has {ncx} columns but y has {ncy} columns") |
|
if ncx == 0 or ncy == 0: |
|
return [] |
|
|
|
label = kwargs.get('label') |
|
n_datasets = max(ncx, ncy) |
|
if n_datasets > 1 and not cbook.is_scalar_or_string(label): |
|
if len(label) != n_datasets: |
|
raise ValueError(f"label must be scalar or have the same " |
|
f"length as the input data, but found " |
|
f"{len(label)} for {n_datasets} datasets.") |
|
labels = label |
|
else: |
|
labels = [label] * n_datasets |
|
|
|
result = (make_artist(axes, x[:, j % ncx], y[:, j % ncy], kw, |
|
{**kwargs, 'label': label}) |
|
for j, label in enumerate(labels)) |
|
|
|
if return_kwargs: |
|
return list(result) |
|
else: |
|
return [l[0] for l in result] |
|
|
|
|
|
@_api.define_aliases({"facecolor": ["fc"]}) |
|
class _AxesBase(martist.Artist): |
|
name = "rectilinear" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_axis_names = ("x", "y") |
|
_shared_axes = {name: cbook.Grouper() for name in _axis_names} |
|
_twinned_axes = cbook.Grouper() |
|
|
|
_subclass_uses_cla = False |
|
|
|
@property |
|
def _axis_map(self): |
|
"""A mapping of axis names, e.g. 'x', to `Axis` instances.""" |
|
return {name: getattr(self, f"{name}axis") |
|
for name in self._axis_names} |
|
|
|
def __str__(self): |
|
return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format( |
|
type(self).__name__, self._position.bounds) |
|
|
|
def __init__(self, fig, |
|
*args, |
|
facecolor=None, |
|
frameon=True, |
|
sharex=None, |
|
sharey=None, |
|
label='', |
|
xscale=None, |
|
yscale=None, |
|
box_aspect=None, |
|
**kwargs |
|
): |
|
""" |
|
Build an Axes in a figure. |
|
|
|
Parameters |
|
---------- |
|
fig : `~matplotlib.figure.Figure` |
|
The Axes is built in the `.Figure` *fig*. |
|
|
|
*args |
|
``*args`` can be a single ``(left, bottom, width, height)`` |
|
rectangle or a single `.Bbox`. This specifies the rectangle (in |
|
figure coordinates) where the Axes is positioned. |
|
|
|
``*args`` can also consist of three numbers or a single three-digit |
|
number; in the latter case, the digits are considered as |
|
independent numbers. The numbers are interpreted as ``(nrows, |
|
ncols, index)``: ``(nrows, ncols)`` specifies the size of an array |
|
of subplots, and ``index`` is the 1-based index of the subplot |
|
being created. Finally, ``*args`` can also directly be a |
|
`.SubplotSpec` instance. |
|
|
|
sharex, sharey : `~matplotlib.axes.Axes`, optional |
|
The x- or y-`~.matplotlib.axis` is shared with the x- or y-axis in |
|
the input `~.axes.Axes`. |
|
|
|
frameon : bool, default: True |
|
Whether the Axes frame is visible. |
|
|
|
box_aspect : float, optional |
|
Set a fixed aspect for the Axes box, i.e. the ratio of height to |
|
width. See `~.axes.Axes.set_box_aspect` for details. |
|
|
|
**kwargs |
|
Other optional keyword arguments: |
|
|
|
%(Axes:kwdoc)s |
|
|
|
Returns |
|
------- |
|
`~.axes.Axes` |
|
The new `~.axes.Axes` object. |
|
""" |
|
|
|
super().__init__() |
|
if "rect" in kwargs: |
|
if args: |
|
raise TypeError( |
|
"'rect' cannot be used together with positional arguments") |
|
rect = kwargs.pop("rect") |
|
_api.check_isinstance((mtransforms.Bbox, Iterable), rect=rect) |
|
args = (rect,) |
|
subplotspec = None |
|
if len(args) == 1 and isinstance(args[0], mtransforms.Bbox): |
|
self._position = args[0] |
|
elif len(args) == 1 and np.iterable(args[0]): |
|
self._position = mtransforms.Bbox.from_bounds(*args[0]) |
|
else: |
|
self._position = self._originalPosition = mtransforms.Bbox.unit() |
|
subplotspec = SubplotSpec._from_subplot_args(fig, args) |
|
if self._position.width < 0 or self._position.height < 0: |
|
raise ValueError('Width and height specified must be non-negative') |
|
self._originalPosition = self._position.frozen() |
|
self.axes = self |
|
self._aspect = 'auto' |
|
self._adjustable = 'box' |
|
self._anchor = 'C' |
|
self._stale_viewlims = {name: False for name in self._axis_names} |
|
self._sharex = sharex |
|
self._sharey = sharey |
|
self.set_label(label) |
|
self.set_figure(fig) |
|
|
|
|
|
|
|
if subplotspec: |
|
self.set_subplotspec(subplotspec) |
|
else: |
|
self._subplotspec = None |
|
self.set_box_aspect(box_aspect) |
|
self._axes_locator = None |
|
|
|
self._children = [] |
|
|
|
|
|
|
|
self._colorbars = [] |
|
self.spines = mspines.Spines.from_dict(self._gen_axes_spines()) |
|
|
|
|
|
self._init_axis() |
|
if facecolor is None: |
|
facecolor = mpl.rcParams['axes.facecolor'] |
|
self._facecolor = facecolor |
|
self._frameon = frameon |
|
self.set_axisbelow(mpl.rcParams['axes.axisbelow']) |
|
|
|
self._rasterization_zorder = None |
|
self.clear() |
|
|
|
|
|
self.fmt_xdata = None |
|
self.fmt_ydata = None |
|
|
|
self.set_navigate(True) |
|
self.set_navigate_mode(None) |
|
|
|
if xscale: |
|
self.set_xscale(xscale) |
|
if yscale: |
|
self.set_yscale(yscale) |
|
|
|
self._internal_update(kwargs) |
|
|
|
for name, axis in self._axis_map.items(): |
|
axis.callbacks._connect_picklable( |
|
'units', self._unit_change_handler(name)) |
|
|
|
rcParams = mpl.rcParams |
|
self.tick_params( |
|
top=rcParams['xtick.top'] and rcParams['xtick.minor.top'], |
|
bottom=rcParams['xtick.bottom'] and rcParams['xtick.minor.bottom'], |
|
labeltop=(rcParams['xtick.labeltop'] and |
|
rcParams['xtick.minor.top']), |
|
labelbottom=(rcParams['xtick.labelbottom'] and |
|
rcParams['xtick.minor.bottom']), |
|
left=rcParams['ytick.left'] and rcParams['ytick.minor.left'], |
|
right=rcParams['ytick.right'] and rcParams['ytick.minor.right'], |
|
labelleft=(rcParams['ytick.labelleft'] and |
|
rcParams['ytick.minor.left']), |
|
labelright=(rcParams['ytick.labelright'] and |
|
rcParams['ytick.minor.right']), |
|
which='minor') |
|
|
|
self.tick_params( |
|
top=rcParams['xtick.top'] and rcParams['xtick.major.top'], |
|
bottom=rcParams['xtick.bottom'] and rcParams['xtick.major.bottom'], |
|
labeltop=(rcParams['xtick.labeltop'] and |
|
rcParams['xtick.major.top']), |
|
labelbottom=(rcParams['xtick.labelbottom'] and |
|
rcParams['xtick.major.bottom']), |
|
left=rcParams['ytick.left'] and rcParams['ytick.major.left'], |
|
right=rcParams['ytick.right'] and rcParams['ytick.major.right'], |
|
labelleft=(rcParams['ytick.labelleft'] and |
|
rcParams['ytick.major.left']), |
|
labelright=(rcParams['ytick.labelright'] and |
|
rcParams['ytick.major.right']), |
|
which='major') |
|
|
|
def __init_subclass__(cls, **kwargs): |
|
parent_uses_cla = super(cls, cls)._subclass_uses_cla |
|
if 'cla' in cls.__dict__: |
|
_api.warn_deprecated( |
|
'3.6', |
|
pending=True, |
|
message=f'Overriding `Axes.cla` in {cls.__qualname__} is ' |
|
'pending deprecation in %(since)s and will be fully ' |
|
'deprecated in favor of `Axes.clear` in the future. ' |
|
'Please report ' |
|
f'this to the {cls.__module__!r} author.') |
|
cls._subclass_uses_cla = 'cla' in cls.__dict__ or parent_uses_cla |
|
super().__init_subclass__(**kwargs) |
|
|
|
def __getstate__(self): |
|
state = super().__getstate__() |
|
|
|
state["_shared_axes"] = { |
|
name: self._shared_axes[name].get_siblings(self) |
|
for name in self._axis_names if self in self._shared_axes[name]} |
|
state["_twinned_axes"] = (self._twinned_axes.get_siblings(self) |
|
if self in self._twinned_axes else None) |
|
return state |
|
|
|
def __setstate__(self, state): |
|
|
|
shared_axes = state.pop("_shared_axes") |
|
for name, shared_siblings in shared_axes.items(): |
|
self._shared_axes[name].join(*shared_siblings) |
|
twinned_siblings = state.pop("_twinned_axes") |
|
if twinned_siblings: |
|
self._twinned_axes.join(*twinned_siblings) |
|
self.__dict__ = state |
|
self._stale = True |
|
|
|
def __repr__(self): |
|
fields = [] |
|
if self.get_label(): |
|
fields += [f"label={self.get_label()!r}"] |
|
if hasattr(self, "get_title"): |
|
titles = {} |
|
for k in ["left", "center", "right"]: |
|
title = self.get_title(loc=k) |
|
if title: |
|
titles[k] = title |
|
if titles: |
|
fields += [f"title={titles}"] |
|
for name, axis in self._axis_map.items(): |
|
if axis.get_label() and axis.get_label().get_text(): |
|
fields += [f"{name}label={axis.get_label().get_text()!r}"] |
|
return f"<{self.__class__.__name__}: " + ", ".join(fields) + ">" |
|
|
|
def get_subplotspec(self): |
|
"""Return the `.SubplotSpec` associated with the subplot, or None.""" |
|
return self._subplotspec |
|
|
|
def set_subplotspec(self, subplotspec): |
|
"""Set the `.SubplotSpec`. associated with the subplot.""" |
|
self._subplotspec = subplotspec |
|
self._set_position(subplotspec.get_position(self.figure)) |
|
|
|
def get_gridspec(self): |
|
"""Return the `.GridSpec` associated with the subplot, or None.""" |
|
return self._subplotspec.get_gridspec() if self._subplotspec else None |
|
|
|
def get_window_extent(self, renderer=None): |
|
""" |
|
Return the Axes bounding box in display space. |
|
|
|
This bounding box does not include the spines, ticks, ticklabels, |
|
or other labels. For a bounding box including these elements use |
|
`~matplotlib.axes.Axes.get_tightbbox`. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.get_tightbbox |
|
matplotlib.axis.Axis.get_tightbbox |
|
matplotlib.spines.Spine.get_window_extent |
|
""" |
|
return self.bbox |
|
|
|
def _init_axis(self): |
|
|
|
self.xaxis = maxis.XAxis(self, clear=False) |
|
self.spines.bottom.register_axis(self.xaxis) |
|
self.spines.top.register_axis(self.xaxis) |
|
self.yaxis = maxis.YAxis(self, clear=False) |
|
self.spines.left.register_axis(self.yaxis) |
|
self.spines.right.register_axis(self.yaxis) |
|
|
|
def set_figure(self, fig): |
|
|
|
super().set_figure(fig) |
|
|
|
self.bbox = mtransforms.TransformedBbox(self._position, |
|
fig.transSubfigure) |
|
|
|
self.dataLim = mtransforms.Bbox.null() |
|
self._viewLim = mtransforms.Bbox.unit() |
|
self.transScale = mtransforms.TransformWrapper( |
|
mtransforms.IdentityTransform()) |
|
|
|
self._set_lim_and_transforms() |
|
|
|
def _unstale_viewLim(self): |
|
|
|
|
|
need_scale = { |
|
name: any(ax._stale_viewlims[name] |
|
for ax in self._shared_axes[name].get_siblings(self)) |
|
for name in self._axis_names} |
|
if any(need_scale.values()): |
|
for name in need_scale: |
|
for ax in self._shared_axes[name].get_siblings(self): |
|
ax._stale_viewlims[name] = False |
|
self.autoscale_view(**{f"scale{name}": scale |
|
for name, scale in need_scale.items()}) |
|
|
|
@property |
|
def viewLim(self): |
|
self._unstale_viewLim() |
|
return self._viewLim |
|
|
|
def _request_autoscale_view(self, axis="all", tight=None): |
|
""" |
|
Mark a single axis, or all of them, as stale wrt. autoscaling. |
|
|
|
No computation is performed until the next autoscaling; thus, separate |
|
calls to control individual axises incur negligible performance cost. |
|
|
|
Parameters |
|
---------- |
|
axis : str, default: "all" |
|
Either an element of ``self._axis_names``, or "all". |
|
tight : bool or None, default: None |
|
""" |
|
axis_names = _api.check_getitem( |
|
{**{k: [k] for k in self._axis_names}, "all": self._axis_names}, |
|
axis=axis) |
|
for name in axis_names: |
|
self._stale_viewlims[name] = True |
|
if tight is not None: |
|
self._tight = tight |
|
|
|
def _set_lim_and_transforms(self): |
|
""" |
|
Set the *_xaxis_transform*, *_yaxis_transform*, *transScale*, |
|
*transData*, *transLimits* and *transAxes* transformations. |
|
|
|
.. note:: |
|
|
|
This method is primarily used by rectilinear projections of the |
|
`~matplotlib.axes.Axes` class, and is meant to be overridden by |
|
new kinds of projection Axes that need different transformations |
|
and limits. (See `~matplotlib.projections.polar.PolarAxes` for an |
|
example.) |
|
""" |
|
self.transAxes = mtransforms.BboxTransformTo(self.bbox) |
|
|
|
|
|
|
|
|
|
self.transScale = mtransforms.TransformWrapper( |
|
mtransforms.IdentityTransform()) |
|
|
|
|
|
|
|
self.transLimits = mtransforms.BboxTransformFrom( |
|
mtransforms.TransformedBbox(self._viewLim, self.transScale)) |
|
|
|
|
|
|
|
|
|
self.transData = self.transScale + (self.transLimits + self.transAxes) |
|
|
|
self._xaxis_transform = mtransforms.blended_transform_factory( |
|
self.transData, self.transAxes) |
|
self._yaxis_transform = mtransforms.blended_transform_factory( |
|
self.transAxes, self.transData) |
|
|
|
def get_xaxis_transform(self, which='grid'): |
|
""" |
|
Get the transformation used for drawing x-axis labels, ticks |
|
and gridlines. The x-direction is in data coordinates and the |
|
y-direction is in axis coordinates. |
|
|
|
.. note:: |
|
|
|
This transformation is primarily used by the |
|
`~matplotlib.axis.Axis` class, and is meant to be |
|
overridden by new kinds of projections that may need to |
|
place axis elements in different locations. |
|
|
|
Parameters |
|
---------- |
|
which : {'grid', 'tick1', 'tick2'} |
|
""" |
|
if which == 'grid': |
|
return self._xaxis_transform |
|
elif which == 'tick1': |
|
|
|
return self.spines.bottom.get_spine_transform() |
|
elif which == 'tick2': |
|
|
|
return self.spines.top.get_spine_transform() |
|
else: |
|
raise ValueError(f'unknown value for which: {which!r}') |
|
|
|
def get_xaxis_text1_transform(self, pad_points): |
|
""" |
|
Returns |
|
------- |
|
transform : Transform |
|
The transform used for drawing x-axis labels, which will add |
|
*pad_points* of padding (in points) between the axis and the label. |
|
The x-direction is in data coordinates and the y-direction is in |
|
axis coordinates |
|
valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} |
|
The text vertical alignment. |
|
halign : {'center', 'left', 'right'} |
|
The text horizontal alignment. |
|
|
|
Notes |
|
----- |
|
This transformation is primarily used by the `~matplotlib.axis.Axis` |
|
class, and is meant to be overridden by new kinds of projections that |
|
may need to place axis elements in different locations. |
|
""" |
|
labels_align = mpl.rcParams["xtick.alignment"] |
|
return (self.get_xaxis_transform(which='tick1') + |
|
mtransforms.ScaledTranslation(0, -1 * pad_points / 72, |
|
self.figure.dpi_scale_trans), |
|
"top", labels_align) |
|
|
|
def get_xaxis_text2_transform(self, pad_points): |
|
""" |
|
Returns |
|
------- |
|
transform : Transform |
|
The transform used for drawing secondary x-axis labels, which will |
|
add *pad_points* of padding (in points) between the axis and the |
|
label. The x-direction is in data coordinates and the y-direction |
|
is in axis coordinates |
|
valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} |
|
The text vertical alignment. |
|
halign : {'center', 'left', 'right'} |
|
The text horizontal alignment. |
|
|
|
Notes |
|
----- |
|
This transformation is primarily used by the `~matplotlib.axis.Axis` |
|
class, and is meant to be overridden by new kinds of projections that |
|
may need to place axis elements in different locations. |
|
""" |
|
labels_align = mpl.rcParams["xtick.alignment"] |
|
return (self.get_xaxis_transform(which='tick2') + |
|
mtransforms.ScaledTranslation(0, pad_points / 72, |
|
self.figure.dpi_scale_trans), |
|
"bottom", labels_align) |
|
|
|
def get_yaxis_transform(self, which='grid'): |
|
""" |
|
Get the transformation used for drawing y-axis labels, ticks |
|
and gridlines. The x-direction is in axis coordinates and the |
|
y-direction is in data coordinates. |
|
|
|
.. note:: |
|
|
|
This transformation is primarily used by the |
|
`~matplotlib.axis.Axis` class, and is meant to be |
|
overridden by new kinds of projections that may need to |
|
place axis elements in different locations. |
|
|
|
Parameters |
|
---------- |
|
which : {'grid', 'tick1', 'tick2'} |
|
""" |
|
if which == 'grid': |
|
return self._yaxis_transform |
|
elif which == 'tick1': |
|
|
|
return self.spines.left.get_spine_transform() |
|
elif which == 'tick2': |
|
|
|
return self.spines.right.get_spine_transform() |
|
else: |
|
raise ValueError(f'unknown value for which: {which!r}') |
|
|
|
def get_yaxis_text1_transform(self, pad_points): |
|
""" |
|
Returns |
|
------- |
|
transform : Transform |
|
The transform used for drawing y-axis labels, which will add |
|
*pad_points* of padding (in points) between the axis and the label. |
|
The x-direction is in axis coordinates and the y-direction is in |
|
data coordinates |
|
valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} |
|
The text vertical alignment. |
|
halign : {'center', 'left', 'right'} |
|
The text horizontal alignment. |
|
|
|
Notes |
|
----- |
|
This transformation is primarily used by the `~matplotlib.axis.Axis` |
|
class, and is meant to be overridden by new kinds of projections that |
|
may need to place axis elements in different locations. |
|
""" |
|
labels_align = mpl.rcParams["ytick.alignment"] |
|
return (self.get_yaxis_transform(which='tick1') + |
|
mtransforms.ScaledTranslation(-1 * pad_points / 72, 0, |
|
self.figure.dpi_scale_trans), |
|
labels_align, "right") |
|
|
|
def get_yaxis_text2_transform(self, pad_points): |
|
""" |
|
Returns |
|
------- |
|
transform : Transform |
|
The transform used for drawing secondart y-axis labels, which will |
|
add *pad_points* of padding (in points) between the axis and the |
|
label. The x-direction is in axis coordinates and the y-direction |
|
is in data coordinates |
|
valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'} |
|
The text vertical alignment. |
|
halign : {'center', 'left', 'right'} |
|
The text horizontal alignment. |
|
|
|
Notes |
|
----- |
|
This transformation is primarily used by the `~matplotlib.axis.Axis` |
|
class, and is meant to be overridden by new kinds of projections that |
|
may need to place axis elements in different locations. |
|
""" |
|
labels_align = mpl.rcParams["ytick.alignment"] |
|
return (self.get_yaxis_transform(which='tick2') + |
|
mtransforms.ScaledTranslation(pad_points / 72, 0, |
|
self.figure.dpi_scale_trans), |
|
labels_align, "left") |
|
|
|
def _update_transScale(self): |
|
self.transScale.set( |
|
mtransforms.blended_transform_factory( |
|
self.xaxis.get_transform(), self.yaxis.get_transform())) |
|
|
|
def get_position(self, original=False): |
|
""" |
|
Return the position of the Axes within the figure as a `.Bbox`. |
|
|
|
Parameters |
|
---------- |
|
original : bool |
|
If ``True``, return the original position. Otherwise, return the |
|
active position. For an explanation of the positions see |
|
`.set_position`. |
|
|
|
Returns |
|
------- |
|
`.Bbox` |
|
|
|
""" |
|
if original: |
|
return self._originalPosition.frozen() |
|
else: |
|
locator = self.get_axes_locator() |
|
if not locator: |
|
self.apply_aspect() |
|
return self._position.frozen() |
|
|
|
def set_position(self, pos, which='both'): |
|
""" |
|
Set the Axes position. |
|
|
|
Axes have two position attributes. The 'original' position is the |
|
position allocated for the Axes. The 'active' position is the |
|
position the Axes is actually drawn at. These positions are usually |
|
the same unless a fixed aspect is set to the Axes. See |
|
`.Axes.set_aspect` for details. |
|
|
|
Parameters |
|
---------- |
|
pos : [left, bottom, width, height] or `~matplotlib.transforms.Bbox` |
|
The new position of the Axes in `.Figure` coordinates. |
|
|
|
which : {'both', 'active', 'original'}, default: 'both' |
|
Determines which position variables to change. |
|
|
|
See Also |
|
-------- |
|
matplotlib.transforms.Bbox.from_bounds |
|
matplotlib.transforms.Bbox.from_extents |
|
""" |
|
self._set_position(pos, which=which) |
|
|
|
|
|
self.set_in_layout(False) |
|
|
|
def _set_position(self, pos, which='both'): |
|
""" |
|
Private version of set_position. |
|
|
|
Call this internally to get the same functionality of `set_position`, |
|
but not to take the axis out of the constrained_layout hierarchy. |
|
""" |
|
if not isinstance(pos, mtransforms.BboxBase): |
|
pos = mtransforms.Bbox.from_bounds(*pos) |
|
for ax in self._twinned_axes.get_siblings(self): |
|
if which in ('both', 'active'): |
|
ax._position.set(pos) |
|
if which in ('both', 'original'): |
|
ax._originalPosition.set(pos) |
|
self.stale = True |
|
|
|
def reset_position(self): |
|
""" |
|
Reset the active position to the original position. |
|
|
|
This undoes changes to the active position (as defined in |
|
`.set_position`) which may have been performed to satisfy fixed-aspect |
|
constraints. |
|
""" |
|
for ax in self._twinned_axes.get_siblings(self): |
|
pos = ax.get_position(original=True) |
|
ax.set_position(pos, which='active') |
|
|
|
def set_axes_locator(self, locator): |
|
""" |
|
Set the Axes locator. |
|
|
|
Parameters |
|
---------- |
|
locator : Callable[[Axes, Renderer], Bbox] |
|
""" |
|
self._axes_locator = locator |
|
self.stale = True |
|
|
|
def get_axes_locator(self): |
|
""" |
|
Return the axes_locator. |
|
""" |
|
return self._axes_locator |
|
|
|
def _set_artist_props(self, a): |
|
"""Set the boilerplate props for artists added to Axes.""" |
|
a.set_figure(self.figure) |
|
if not a.is_transform_set(): |
|
a.set_transform(self.transData) |
|
|
|
a.axes = self |
|
if a.get_mouseover(): |
|
self._mouseover_set.add(a) |
|
|
|
def _gen_axes_patch(self): |
|
""" |
|
Returns |
|
------- |
|
Patch |
|
The patch used to draw the background of the Axes. It is also used |
|
as the clipping path for any data elements on the Axes. |
|
|
|
In the standard Axes, this is a rectangle, but in other projections |
|
it may not be. |
|
|
|
Notes |
|
----- |
|
Intended to be overridden by new projection types. |
|
""" |
|
return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0) |
|
|
|
def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'): |
|
""" |
|
Returns |
|
------- |
|
dict |
|
Mapping of spine names to `.Line2D` or `.Patch` instances that are |
|
used to draw Axes spines. |
|
|
|
In the standard Axes, spines are single line segments, but in other |
|
projections they may not be. |
|
|
|
Notes |
|
----- |
|
Intended to be overridden by new projection types. |
|
""" |
|
return {side: mspines.Spine.linear_spine(self, side) |
|
for side in ['left', 'right', 'bottom', 'top']} |
|
|
|
def sharex(self, other): |
|
""" |
|
Share the x-axis with *other*. |
|
|
|
This is equivalent to passing ``sharex=other`` when constructing the |
|
Axes, and cannot be used if the x-axis is already being shared with |
|
another Axes. |
|
""" |
|
_api.check_isinstance(_AxesBase, other=other) |
|
if self._sharex is not None and other is not self._sharex: |
|
raise ValueError("x-axis is already shared") |
|
self._shared_axes["x"].join(self, other) |
|
self._sharex = other |
|
self.xaxis.major = other.xaxis.major |
|
self.xaxis.minor = other.xaxis.minor |
|
x0, x1 = other.get_xlim() |
|
self.set_xlim(x0, x1, emit=False, auto=other.get_autoscalex_on()) |
|
self.xaxis._scale = other.xaxis._scale |
|
|
|
def sharey(self, other): |
|
""" |
|
Share the y-axis with *other*. |
|
|
|
This is equivalent to passing ``sharey=other`` when constructing the |
|
Axes, and cannot be used if the y-axis is already being shared with |
|
another Axes. |
|
""" |
|
_api.check_isinstance(_AxesBase, other=other) |
|
if self._sharey is not None and other is not self._sharey: |
|
raise ValueError("y-axis is already shared") |
|
self._shared_axes["y"].join(self, other) |
|
self._sharey = other |
|
self.yaxis.major = other.yaxis.major |
|
self.yaxis.minor = other.yaxis.minor |
|
y0, y1 = other.get_ylim() |
|
self.set_ylim(y0, y1, emit=False, auto=other.get_autoscaley_on()) |
|
self.yaxis._scale = other.yaxis._scale |
|
|
|
def __clear(self): |
|
"""Clear the Axes.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(self, 'patch'): |
|
patch_visible = self.patch.get_visible() |
|
else: |
|
patch_visible = True |
|
|
|
xaxis_visible = self.xaxis.get_visible() |
|
yaxis_visible = self.yaxis.get_visible() |
|
|
|
for axis in self._axis_map.values(): |
|
axis.clear() |
|
for spine in self.spines.values(): |
|
spine._clear() |
|
|
|
self.ignore_existing_data_limits = True |
|
self.callbacks = cbook.CallbackRegistry( |
|
signals=["xlim_changed", "ylim_changed", "zlim_changed"]) |
|
|
|
|
|
if mpl.rcParams['xtick.minor.visible']: |
|
self.xaxis.set_minor_locator(mticker.AutoMinorLocator()) |
|
if mpl.rcParams['ytick.minor.visible']: |
|
self.yaxis.set_minor_locator(mticker.AutoMinorLocator()) |
|
|
|
self._xmargin = mpl.rcParams['axes.xmargin'] |
|
self._ymargin = mpl.rcParams['axes.ymargin'] |
|
self._tight = None |
|
self._use_sticky_edges = True |
|
|
|
self._get_lines = _process_plot_var_args() |
|
self._get_patches_for_fill = _process_plot_var_args('fill') |
|
|
|
self._gridOn = mpl.rcParams['axes.grid'] |
|
old_children, self._children = self._children, [] |
|
for chld in old_children: |
|
chld.axes = chld.figure = None |
|
self._mouseover_set = _OrderedSet() |
|
self.child_axes = [] |
|
self._current_image = None |
|
self._projection_init = None |
|
self.legend_ = None |
|
self.containers = [] |
|
|
|
self.grid(False) |
|
self.grid(self._gridOn, which=mpl.rcParams['axes.grid.which'], |
|
axis=mpl.rcParams['axes.grid.axis']) |
|
props = font_manager.FontProperties( |
|
size=mpl.rcParams['axes.titlesize'], |
|
weight=mpl.rcParams['axes.titleweight']) |
|
|
|
y = mpl.rcParams['axes.titley'] |
|
if y is None: |
|
y = 1.0 |
|
self._autotitlepos = True |
|
else: |
|
self._autotitlepos = False |
|
|
|
self.title = mtext.Text( |
|
x=0.5, y=y, text='', |
|
fontproperties=props, |
|
verticalalignment='baseline', |
|
horizontalalignment='center', |
|
) |
|
self._left_title = mtext.Text( |
|
x=0.0, y=y, text='', |
|
fontproperties=props.copy(), |
|
verticalalignment='baseline', |
|
horizontalalignment='left', ) |
|
self._right_title = mtext.Text( |
|
x=1.0, y=y, text='', |
|
fontproperties=props.copy(), |
|
verticalalignment='baseline', |
|
horizontalalignment='right', |
|
) |
|
title_offset_points = mpl.rcParams['axes.titlepad'] |
|
|
|
|
|
self._set_title_offset_trans(title_offset_points) |
|
|
|
for _title in (self.title, self._left_title, self._right_title): |
|
self._set_artist_props(_title) |
|
|
|
|
|
|
|
|
|
self.patch = self._gen_axes_patch() |
|
self.patch.set_figure(self.figure) |
|
self.patch.set_facecolor(self._facecolor) |
|
self.patch.set_edgecolor('none') |
|
self.patch.set_linewidth(0) |
|
self.patch.set_transform(self.transAxes) |
|
|
|
self.set_axis_on() |
|
|
|
self.xaxis.set_clip_path(self.patch) |
|
self.yaxis.set_clip_path(self.patch) |
|
|
|
if self._sharex is not None: |
|
self.xaxis.set_visible(xaxis_visible) |
|
self.patch.set_visible(patch_visible) |
|
if self._sharey is not None: |
|
self.yaxis.set_visible(yaxis_visible) |
|
self.patch.set_visible(patch_visible) |
|
|
|
|
|
|
|
for name, axis in self._axis_map.items(): |
|
share = getattr(self, f"_share{name}") |
|
if share is not None: |
|
getattr(self, f"share{name}")(share) |
|
else: |
|
|
|
|
|
if self.name == "polar": |
|
axis._set_scale("linear") |
|
axis._set_lim(0, 1, auto=True) |
|
self._update_transScale() |
|
|
|
self.stale = True |
|
|
|
def clear(self): |
|
"""Clear the Axes.""" |
|
|
|
|
|
if self._subclass_uses_cla: |
|
self.cla() |
|
else: |
|
self.__clear() |
|
|
|
def cla(self): |
|
"""Clear the Axes.""" |
|
|
|
|
|
if self._subclass_uses_cla: |
|
self.__clear() |
|
else: |
|
self.clear() |
|
|
|
class ArtistList(Sequence): |
|
""" |
|
A sublist of Axes children based on their type. |
|
|
|
The type-specific children sublists were made immutable in Matplotlib |
|
3.7. In the future these artist lists may be replaced by tuples. Use |
|
as if this is a tuple already. |
|
""" |
|
def __init__(self, axes, prop_name, |
|
valid_types=None, invalid_types=None): |
|
""" |
|
Parameters |
|
---------- |
|
axes : `~matplotlib.axes.Axes` |
|
The Axes from which this sublist will pull the children |
|
Artists. |
|
prop_name : str |
|
The property name used to access this sublist from the Axes; |
|
used to generate deprecation warnings. |
|
valid_types : list of type, optional |
|
A list of types that determine which children will be returned |
|
by this sublist. If specified, then the Artists in the sublist |
|
must be instances of any of these types. If unspecified, then |
|
any type of Artist is valid (unless limited by |
|
*invalid_types*.) |
|
invalid_types : tuple, optional |
|
A list of types that determine which children will *not* be |
|
returned by this sublist. If specified, then Artists in the |
|
sublist will never be an instance of these types. Otherwise, no |
|
types will be excluded. |
|
""" |
|
self._axes = axes |
|
self._prop_name = prop_name |
|
self._type_check = lambda artist: ( |
|
(not valid_types or isinstance(artist, valid_types)) and |
|
(not invalid_types or not isinstance(artist, invalid_types)) |
|
) |
|
|
|
def __repr__(self): |
|
return f'<Axes.ArtistList of {len(self)} {self._prop_name}>' |
|
|
|
def __len__(self): |
|
return sum(self._type_check(artist) |
|
for artist in self._axes._children) |
|
|
|
def __iter__(self): |
|
for artist in list(self._axes._children): |
|
if self._type_check(artist): |
|
yield artist |
|
|
|
def __getitem__(self, key): |
|
return [artist |
|
for artist in self._axes._children |
|
if self._type_check(artist)][key] |
|
|
|
def __add__(self, other): |
|
if isinstance(other, (list, _AxesBase.ArtistList)): |
|
return [*self, *other] |
|
if isinstance(other, (tuple, _AxesBase.ArtistList)): |
|
return (*self, *other) |
|
return NotImplemented |
|
|
|
def __radd__(self, other): |
|
if isinstance(other, list): |
|
return other + list(self) |
|
if isinstance(other, tuple): |
|
return other + tuple(self) |
|
return NotImplemented |
|
|
|
@property |
|
def artists(self): |
|
return self.ArtistList(self, 'artists', invalid_types=( |
|
mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch, |
|
mtable.Table, mtext.Text)) |
|
|
|
@property |
|
def collections(self): |
|
return self.ArtistList(self, 'collections', |
|
valid_types=mcoll.Collection) |
|
|
|
@property |
|
def images(self): |
|
return self.ArtistList(self, 'images', valid_types=mimage.AxesImage) |
|
|
|
@property |
|
def lines(self): |
|
return self.ArtistList(self, 'lines', valid_types=mlines.Line2D) |
|
|
|
@property |
|
def patches(self): |
|
return self.ArtistList(self, 'patches', valid_types=mpatches.Patch) |
|
|
|
@property |
|
def tables(self): |
|
return self.ArtistList(self, 'tables', valid_types=mtable.Table) |
|
|
|
@property |
|
def texts(self): |
|
return self.ArtistList(self, 'texts', valid_types=mtext.Text) |
|
|
|
def get_facecolor(self): |
|
"""Get the facecolor of the Axes.""" |
|
return self.patch.get_facecolor() |
|
|
|
def set_facecolor(self, color): |
|
""" |
|
Set the facecolor of the Axes. |
|
|
|
Parameters |
|
---------- |
|
color : color |
|
""" |
|
self._facecolor = color |
|
self.stale = True |
|
return self.patch.set_facecolor(color) |
|
|
|
def _set_title_offset_trans(self, title_offset_points): |
|
""" |
|
Set the offset for the title either from :rc:`axes.titlepad` |
|
or from set_title kwarg ``pad``. |
|
""" |
|
self.titleOffsetTrans = mtransforms.ScaledTranslation( |
|
0.0, title_offset_points / 72, |
|
self.figure.dpi_scale_trans) |
|
for _title in (self.title, self._left_title, self._right_title): |
|
_title.set_transform(self.transAxes + self.titleOffsetTrans) |
|
_title.set_clip_box(None) |
|
|
|
def set_prop_cycle(self, *args, **kwargs): |
|
""" |
|
Set the property cycle of the Axes. |
|
|
|
The property cycle controls the style properties such as color, |
|
marker and linestyle of future plot commands. The style properties |
|
of data already added to the Axes are not modified. |
|
|
|
Call signatures:: |
|
|
|
set_prop_cycle(cycler) |
|
set_prop_cycle(label=values[, label2=values2[, ...]]) |
|
set_prop_cycle(label, values) |
|
|
|
Form 1 sets given `~cycler.Cycler` object. |
|
|
|
Form 2 creates a `~cycler.Cycler` which cycles over one or more |
|
properties simultaneously and set it as the property cycle of the |
|
Axes. If multiple properties are given, their value lists must have |
|
the same length. This is just a shortcut for explicitly creating a |
|
cycler and passing it to the function, i.e. it's short for |
|
``set_prop_cycle(cycler(label=values label2=values2, ...))``. |
|
|
|
Form 3 creates a `~cycler.Cycler` for a single property and set it |
|
as the property cycle of the Axes. This form exists for compatibility |
|
with the original `cycler.cycler` interface. Its use is discouraged |
|
in favor of the kwarg form, i.e. ``set_prop_cycle(label=values)``. |
|
|
|
Parameters |
|
---------- |
|
cycler : `~cycler.Cycler` |
|
Set the given Cycler. *None* resets to the cycle defined by the |
|
current style. |
|
|
|
.. ACCEPTS: `~cycler.Cycler` |
|
|
|
label : str |
|
The property key. Must be a valid `.Artist` property. |
|
For example, 'color' or 'linestyle'. Aliases are allowed, |
|
such as 'c' for 'color' and 'lw' for 'linewidth'. |
|
|
|
values : iterable |
|
Finite-length iterable of the property values. These values |
|
are validated and will raise a ValueError if invalid. |
|
|
|
See Also |
|
-------- |
|
matplotlib.rcsetup.cycler |
|
Convenience function for creating validated cyclers for properties. |
|
cycler.cycler |
|
The original function for creating unvalidated cyclers. |
|
|
|
Examples |
|
-------- |
|
Setting the property cycle for a single property: |
|
|
|
>>> ax.set_prop_cycle(color=['red', 'green', 'blue']) |
|
|
|
Setting the property cycle for simultaneously cycling over multiple |
|
properties (e.g. red circle, green plus, blue cross): |
|
|
|
>>> ax.set_prop_cycle(color=['red', 'green', 'blue'], |
|
... marker=['o', '+', 'x']) |
|
|
|
""" |
|
if args and kwargs: |
|
raise TypeError("Cannot supply both positional and keyword " |
|
"arguments to this method.") |
|
|
|
if len(args) == 1 and args[0] is None: |
|
prop_cycle = None |
|
else: |
|
prop_cycle = cycler(*args, **kwargs) |
|
self._get_lines.set_prop_cycle(prop_cycle) |
|
self._get_patches_for_fill.set_prop_cycle(prop_cycle) |
|
|
|
def get_aspect(self): |
|
""" |
|
Return the aspect ratio of the axes scaling. |
|
|
|
This is either "auto" or a float giving the ratio of y/x-scale. |
|
""" |
|
return self._aspect |
|
|
|
def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): |
|
""" |
|
Set the aspect ratio of the axes scaling, i.e. y/x-scale. |
|
|
|
Parameters |
|
---------- |
|
aspect : {'auto', 'equal'} or float |
|
Possible values: |
|
|
|
- 'auto': fill the position rectangle with data. |
|
- 'equal': same as ``aspect=1``, i.e. same scaling for x and y. |
|
- *float*: The displayed size of 1 unit in y-data coordinates will |
|
be *aspect* times the displayed size of 1 unit in x-data |
|
coordinates; e.g. for ``aspect=2`` a square in data coordinates |
|
will be rendered with a height of twice its width. |
|
|
|
adjustable : None or {'box', 'datalim'}, optional |
|
If not ``None``, this defines which parameter will be adjusted to |
|
meet the required aspect. See `.set_adjustable` for further |
|
details. |
|
|
|
anchor : None or str or (float, float), optional |
|
If not ``None``, this defines where the Axes will be drawn if there |
|
is extra space due to aspect constraints. The most common way |
|
to specify the anchor are abbreviations of cardinal directions: |
|
|
|
===== ===================== |
|
value description |
|
===== ===================== |
|
'C' centered |
|
'SW' lower left corner |
|
'S' middle of bottom edge |
|
'SE' lower right corner |
|
etc. |
|
===== ===================== |
|
|
|
See `~.Axes.set_anchor` for further details. |
|
|
|
share : bool, default: False |
|
If ``True``, apply the settings to all shared Axes. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_adjustable |
|
Set how the Axes adjusts to achieve the required aspect ratio. |
|
matplotlib.axes.Axes.set_anchor |
|
Set the position in case of extra space. |
|
""" |
|
if cbook._str_equal(aspect, 'equal'): |
|
aspect = 1 |
|
if not cbook._str_equal(aspect, 'auto'): |
|
aspect = float(aspect) |
|
if aspect <= 0 or not np.isfinite(aspect): |
|
raise ValueError("aspect must be finite and positive ") |
|
|
|
if share: |
|
axes = {sibling for name in self._axis_names |
|
for sibling in self._shared_axes[name].get_siblings(self)} |
|
else: |
|
axes = [self] |
|
|
|
for ax in axes: |
|
ax._aspect = aspect |
|
|
|
if adjustable is None: |
|
adjustable = self._adjustable |
|
self.set_adjustable(adjustable, share=share) |
|
|
|
if anchor is not None: |
|
self.set_anchor(anchor, share=share) |
|
self.stale = True |
|
|
|
def get_adjustable(self): |
|
""" |
|
Return whether the Axes will adjust its physical dimension ('box') or |
|
its data limits ('datalim') to achieve the desired aspect ratio. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_adjustable |
|
Set how the Axes adjusts to achieve the required aspect ratio. |
|
matplotlib.axes.Axes.set_aspect |
|
For a description of aspect handling. |
|
""" |
|
return self._adjustable |
|
|
|
def set_adjustable(self, adjustable, share=False): |
|
""" |
|
Set how the Axes adjusts to achieve the required aspect ratio. |
|
|
|
Parameters |
|
---------- |
|
adjustable : {'box', 'datalim'} |
|
If 'box', change the physical dimensions of the Axes. |
|
If 'datalim', change the ``x`` or ``y`` data limits. |
|
|
|
share : bool, default: False |
|
If ``True``, apply the settings to all shared Axes. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_aspect |
|
For a description of aspect handling. |
|
|
|
Notes |
|
----- |
|
Shared Axes (of which twinned Axes are a special case) |
|
impose restrictions on how aspect ratios can be imposed. |
|
For twinned Axes, use 'datalim'. For Axes that share both |
|
x and y, use 'box'. Otherwise, either 'datalim' or 'box' |
|
may be used. These limitations are partly a requirement |
|
to avoid over-specification, and partly a result of the |
|
particular implementation we are currently using, in |
|
which the adjustments for aspect ratios are done sequentially |
|
and independently on each Axes as it is drawn. |
|
""" |
|
_api.check_in_list(["box", "datalim"], adjustable=adjustable) |
|
if share: |
|
axs = {sibling for name in self._axis_names |
|
for sibling in self._shared_axes[name].get_siblings(self)} |
|
else: |
|
axs = [self] |
|
if (adjustable == "datalim" |
|
and any(getattr(ax.get_data_ratio, "__func__", None) |
|
!= _AxesBase.get_data_ratio |
|
for ax in axs)): |
|
|
|
|
|
raise ValueError("Cannot set Axes adjustable to 'datalim' for " |
|
"Axes which override 'get_data_ratio'") |
|
for ax in axs: |
|
ax._adjustable = adjustable |
|
self.stale = True |
|
|
|
def get_box_aspect(self): |
|
""" |
|
Return the Axes box aspect, i.e. the ratio of height to width. |
|
|
|
The box aspect is ``None`` (i.e. chosen depending on the available |
|
figure space) unless explicitly specified. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_box_aspect |
|
for a description of box aspect. |
|
matplotlib.axes.Axes.set_aspect |
|
for a description of aspect handling. |
|
""" |
|
return self._box_aspect |
|
|
|
def set_box_aspect(self, aspect=None): |
|
""" |
|
Set the Axes box aspect, i.e. the ratio of height to width. |
|
|
|
This defines the aspect of the Axes in figure space and is not to be |
|
confused with the data aspect (see `~.Axes.set_aspect`). |
|
|
|
Parameters |
|
---------- |
|
aspect : float or None |
|
Changes the physical dimensions of the Axes, such that the ratio |
|
of the Axes height to the Axes width in physical units is equal to |
|
*aspect*. Defining a box aspect will change the *adjustable* |
|
property to 'datalim' (see `~.Axes.set_adjustable`). |
|
|
|
*None* will disable a fixed box aspect so that height and width |
|
of the Axes are chosen independently. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_aspect |
|
for a description of aspect handling. |
|
""" |
|
axs = {*self._twinned_axes.get_siblings(self), |
|
*self._twinned_axes.get_siblings(self)} |
|
|
|
if aspect is not None: |
|
aspect = float(aspect) |
|
|
|
|
|
for ax in axs: |
|
ax.set_adjustable("datalim") |
|
|
|
for ax in axs: |
|
ax._box_aspect = aspect |
|
ax.stale = True |
|
|
|
def get_anchor(self): |
|
""" |
|
Get the anchor location. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_anchor |
|
for a description of the anchor. |
|
matplotlib.axes.Axes.set_aspect |
|
for a description of aspect handling. |
|
""" |
|
return self._anchor |
|
|
|
def set_anchor(self, anchor, share=False): |
|
""" |
|
Define the anchor location. |
|
|
|
The actual drawing area (active position) of the Axes may be smaller |
|
than the Bbox (original position) when a fixed aspect is required. The |
|
anchor defines where the drawing area will be located within the |
|
available space. |
|
|
|
Parameters |
|
---------- |
|
anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', ...} |
|
Either an (*x*, *y*) pair of relative coordinates (0 is left or |
|
bottom, 1 is right or top), 'C' (center), or a cardinal direction |
|
('SW', southwest, is bottom left, etc.). str inputs are shorthands |
|
for (*x*, *y*) coordinates, as shown in the following diagram:: |
|
|
|
┌─────────────────┬─────────────────┬─────────────────┐ |
|
│ 'NW' (0.0, 1.0) │ 'N' (0.5, 1.0) │ 'NE' (1.0, 1.0) │ |
|
├─────────────────┼─────────────────┼─────────────────┤ |
|
│ 'W' (0.0, 0.5) │ 'C' (0.5, 0.5) │ 'E' (1.0, 0.5) │ |
|
├─────────────────┼─────────────────┼─────────────────┤ |
|
│ 'SW' (0.0, 0.0) │ 'S' (0.5, 0.0) │ 'SE' (1.0, 0.0) │ |
|
└─────────────────┴─────────────────┴─────────────────┘ |
|
|
|
share : bool, default: False |
|
If ``True``, apply the settings to all shared Axes. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_aspect |
|
for a description of aspect handling. |
|
""" |
|
if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2): |
|
raise ValueError('argument must be among %s' % |
|
', '.join(mtransforms.Bbox.coefs)) |
|
if share: |
|
axes = {sibling for name in self._axis_names |
|
for sibling in self._shared_axes[name].get_siblings(self)} |
|
else: |
|
axes = [self] |
|
for ax in axes: |
|
ax._anchor = anchor |
|
|
|
self.stale = True |
|
|
|
def get_data_ratio(self): |
|
""" |
|
Return the aspect ratio of the scaled data. |
|
|
|
Notes |
|
----- |
|
This method is intended to be overridden by new projection types. |
|
""" |
|
txmin, txmax = self.xaxis.get_transform().transform(self.get_xbound()) |
|
tymin, tymax = self.yaxis.get_transform().transform(self.get_ybound()) |
|
xsize = max(abs(txmax - txmin), 1e-30) |
|
ysize = max(abs(tymax - tymin), 1e-30) |
|
return ysize / xsize |
|
|
|
def apply_aspect(self, position=None): |
|
""" |
|
Adjust the Axes for a specified data aspect ratio. |
|
|
|
Depending on `.get_adjustable` this will modify either the |
|
Axes box (position) or the view limits. In the former case, |
|
`~matplotlib.axes.Axes.get_anchor` will affect the position. |
|
|
|
Parameters |
|
---------- |
|
position : None or .Bbox |
|
If not ``None``, this defines the position of the |
|
Axes within the figure as a Bbox. See `~.Axes.get_position` |
|
for further details. |
|
|
|
Notes |
|
----- |
|
This is called automatically when each Axes is drawn. You may need |
|
to call it yourself if you need to update the Axes position and/or |
|
view limits before the Figure is drawn. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_aspect |
|
For a description of aspect ratio handling. |
|
matplotlib.axes.Axes.set_adjustable |
|
Set how the Axes adjusts to achieve the required aspect ratio. |
|
matplotlib.axes.Axes.set_anchor |
|
Set the position in case of extra space. |
|
""" |
|
if position is None: |
|
position = self.get_position(original=True) |
|
|
|
aspect = self.get_aspect() |
|
|
|
if aspect == 'auto' and self._box_aspect is None: |
|
self._set_position(position, which='active') |
|
return |
|
|
|
trans = self.get_figure().transSubfigure |
|
bb = mtransforms.Bbox.unit().transformed(trans) |
|
|
|
fig_aspect = bb.height / bb.width |
|
|
|
if self._adjustable == 'box': |
|
if self in self._twinned_axes: |
|
raise RuntimeError("Adjustable 'box' is not allowed in a " |
|
"twinned Axes; use 'datalim' instead") |
|
box_aspect = aspect * self.get_data_ratio() |
|
pb = position.frozen() |
|
pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) |
|
self._set_position(pb1.anchored(self.get_anchor(), pb), 'active') |
|
return |
|
|
|
|
|
if self._box_aspect is not None: |
|
pb = position.frozen() |
|
pb1 = pb.shrunk_to_aspect(self._box_aspect, pb, fig_aspect) |
|
self._set_position(pb1.anchored(self.get_anchor(), pb), 'active') |
|
if aspect == "auto": |
|
return |
|
|
|
|
|
|
|
if self._box_aspect is None: |
|
self._set_position(position, which='active') |
|
else: |
|
position = pb1.anchored(self.get_anchor(), pb) |
|
|
|
x_trf = self.xaxis.get_transform() |
|
y_trf = self.yaxis.get_transform() |
|
xmin, xmax = x_trf.transform(self.get_xbound()) |
|
ymin, ymax = y_trf.transform(self.get_ybound()) |
|
xsize = max(abs(xmax - xmin), 1e-30) |
|
ysize = max(abs(ymax - ymin), 1e-30) |
|
|
|
box_aspect = fig_aspect * (position.height / position.width) |
|
data_ratio = box_aspect / aspect |
|
|
|
y_expander = data_ratio * xsize / ysize - 1 |
|
|
|
if abs(y_expander) < 0.005: |
|
return |
|
|
|
dL = self.dataLim |
|
x0, x1 = x_trf.transform(dL.intervalx) |
|
y0, y1 = y_trf.transform(dL.intervaly) |
|
xr = 1.05 * (x1 - x0) |
|
yr = 1.05 * (y1 - y0) |
|
|
|
xmarg = xsize - xr |
|
ymarg = ysize - yr |
|
Ysize = data_ratio * xsize |
|
Xsize = ysize / data_ratio |
|
Xmarg = Xsize - xr |
|
Ymarg = Ysize - yr |
|
|
|
xm = 0 |
|
ym = 0 |
|
|
|
shared_x = self in self._shared_axes["x"] |
|
shared_y = self in self._shared_axes["y"] |
|
|
|
if shared_x and shared_y: |
|
raise RuntimeError("set_aspect(..., adjustable='datalim') or " |
|
"axis('equal') are not allowed when both axes " |
|
"are shared. Try set_aspect(..., " |
|
"adjustable='box').") |
|
|
|
|
|
if shared_y: |
|
adjust_y = False |
|
else: |
|
if xmarg > xm and ymarg > ym: |
|
adjy = ((Ymarg > 0 and y_expander < 0) or |
|
(Xmarg < 0 and y_expander > 0)) |
|
else: |
|
adjy = y_expander > 0 |
|
adjust_y = shared_x or adjy |
|
|
|
if adjust_y: |
|
yc = 0.5 * (ymin + ymax) |
|
y0 = yc - Ysize / 2.0 |
|
y1 = yc + Ysize / 2.0 |
|
self.set_ybound(y_trf.inverted().transform([y0, y1])) |
|
else: |
|
xc = 0.5 * (xmin + xmax) |
|
x0 = xc - Xsize / 2.0 |
|
x1 = xc + Xsize / 2.0 |
|
self.set_xbound(x_trf.inverted().transform([x0, x1])) |
|
|
|
def axis(self, arg=None, /, *, emit=True, **kwargs): |
|
""" |
|
Convenience method to get or set some axis properties. |
|
|
|
Call signatures:: |
|
|
|
xmin, xmax, ymin, ymax = axis() |
|
xmin, xmax, ymin, ymax = axis([xmin, xmax, ymin, ymax]) |
|
xmin, xmax, ymin, ymax = axis(option) |
|
xmin, xmax, ymin, ymax = axis(**kwargs) |
|
|
|
Parameters |
|
---------- |
|
xmin, xmax, ymin, ymax : float, optional |
|
The axis limits to be set. This can also be achieved using :: |
|
|
|
ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax)) |
|
|
|
option : bool or str |
|
If a bool, turns axis lines and labels on or off. If a string, |
|
possible values are: |
|
|
|
================ =========================================================== |
|
Value Description |
|
================ =========================================================== |
|
'off' or `False` Hide all axis decorations, i.e. axis labels, spines, |
|
tick marks, tick labels, and grid lines. |
|
This is the same as `~.Axes.set_axis_off()`. |
|
'on' or `True` Do not hide all axis decorations, i.e. axis labels, spines, |
|
tick marks, tick labels, and grid lines. |
|
This is the same as `~.Axes.set_axis_on()`. |
|
'equal' Set equal scaling (i.e., make circles circular) by |
|
changing the axis limits. This is the same as |
|
``ax.set_aspect('equal', adjustable='datalim')``. |
|
Explicit data limits may not be respected in this case. |
|
'scaled' Set equal scaling (i.e., make circles circular) by |
|
changing dimensions of the plot box. This is the same as |
|
``ax.set_aspect('equal', adjustable='box', anchor='C')``. |
|
Additionally, further autoscaling will be disabled. |
|
'tight' Set limits just large enough to show all data, then |
|
disable further autoscaling. |
|
'auto' Automatic scaling (fill plot box with data). |
|
'image' 'scaled' with axis limits equal to data limits. |
|
'square' Square plot; similar to 'scaled', but initially forcing |
|
``xmax-xmin == ymax-ymin``. |
|
================ =========================================================== |
|
|
|
emit : bool, default: True |
|
Whether observers are notified of the axis limit change. |
|
This option is passed on to `~.Axes.set_xlim` and |
|
`~.Axes.set_ylim`. |
|
|
|
Returns |
|
------- |
|
xmin, xmax, ymin, ymax : float |
|
The axis limits. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.set_xlim |
|
matplotlib.axes.Axes.set_ylim |
|
|
|
Notes |
|
----- |
|
For 3D axes, this method additionally takes *zmin*, *zmax* as |
|
parameters and likewise returns them. |
|
""" |
|
if isinstance(arg, (str, bool)): |
|
if arg is True: |
|
arg = 'on' |
|
if arg is False: |
|
arg = 'off' |
|
arg = arg.lower() |
|
if arg == 'on': |
|
self.set_axis_on() |
|
elif arg == 'off': |
|
self.set_axis_off() |
|
elif arg in [ |
|
'equal', 'tight', 'scaled', 'auto', 'image', 'square']: |
|
self.set_autoscale_on(True) |
|
self.set_aspect('auto') |
|
self.autoscale_view(tight=False) |
|
if arg == 'equal': |
|
self.set_aspect('equal', adjustable='datalim') |
|
elif arg == 'scaled': |
|
self.set_aspect('equal', adjustable='box', anchor='C') |
|
self.set_autoscale_on(False) |
|
elif arg == 'tight': |
|
self.autoscale_view(tight=True) |
|
self.set_autoscale_on(False) |
|
elif arg == 'image': |
|
self.autoscale_view(tight=True) |
|
self.set_autoscale_on(False) |
|
self.set_aspect('equal', adjustable='box', anchor='C') |
|
elif arg == 'square': |
|
self.set_aspect('equal', adjustable='box', anchor='C') |
|
self.set_autoscale_on(False) |
|
xlim = self.get_xlim() |
|
ylim = self.get_ylim() |
|
edge_size = max(np.diff(xlim), np.diff(ylim))[0] |
|
self.set_xlim([xlim[0], xlim[0] + edge_size], |
|
emit=emit, auto=False) |
|
self.set_ylim([ylim[0], ylim[0] + edge_size], |
|
emit=emit, auto=False) |
|
else: |
|
raise ValueError(f"Unrecognized string {arg!r} to axis; " |
|
"try 'on' or 'off'") |
|
else: |
|
if arg is not None: |
|
if len(arg) != 2*len(self._axis_names): |
|
raise TypeError( |
|
"The first argument to axis() must be an iterable of the form " |
|
"[{}]".format(", ".join( |
|
f"{name}min, {name}max" for name in self._axis_names))) |
|
limits = { |
|
name: arg[2*i:2*(i+1)] |
|
for i, name in enumerate(self._axis_names) |
|
} |
|
else: |
|
limits = {} |
|
for name in self._axis_names: |
|
ax_min = kwargs.pop(f'{name}min', None) |
|
ax_max = kwargs.pop(f'{name}max', None) |
|
limits[name] = (ax_min, ax_max) |
|
for name, (ax_min, ax_max) in limits.items(): |
|
ax_auto = (None |
|
if ax_min is None and ax_max is None |
|
else False) |
|
set_ax_lim = getattr(self, f'set_{name}lim') |
|
set_ax_lim(ax_min, ax_max, emit=emit, auto=ax_auto) |
|
if kwargs: |
|
raise _api.kwarg_error("axis", kwargs) |
|
lims = () |
|
for name in self._axis_names: |
|
get_ax_lim = getattr(self, f'get_{name}lim') |
|
lims += get_ax_lim() |
|
return lims |
|
|
|
def get_legend(self): |
|
"""Return the `.Legend` instance, or None if no legend is defined.""" |
|
return self.legend_ |
|
|
|
def get_images(self): |
|
r"""Return a list of `.AxesImage`\s contained by the Axes.""" |
|
return cbook.silent_list('AxesImage', self.images) |
|
|
|
def get_lines(self): |
|
"""Return a list of lines contained by the Axes.""" |
|
return cbook.silent_list('Line2D', self.lines) |
|
|
|
def get_xaxis(self): |
|
""" |
|
[*Discouraged*] Return the XAxis instance. |
|
|
|
.. admonition:: Discouraged |
|
|
|
The use of this function is discouraged. You should instead |
|
directly access the attribute ``ax.xaxis``. |
|
""" |
|
return self.xaxis |
|
|
|
def get_yaxis(self): |
|
""" |
|
[*Discouraged*] Return the YAxis instance. |
|
|
|
.. admonition:: Discouraged |
|
|
|
The use of this function is discouraged. You should instead |
|
directly access the attribute ``ax.yaxis``. |
|
""" |
|
return self.yaxis |
|
|
|
get_xgridlines = _axis_method_wrapper("xaxis", "get_gridlines") |
|
get_xticklines = _axis_method_wrapper("xaxis", "get_ticklines") |
|
get_ygridlines = _axis_method_wrapper("yaxis", "get_gridlines") |
|
get_yticklines = _axis_method_wrapper("yaxis", "get_ticklines") |
|
|
|
|
|
|
|
def _sci(self, im): |
|
""" |
|
Set the current image. |
|
|
|
This image will be the target of colormap functions like |
|
``pyplot.viridis``, and other functions such as `~.pyplot.clim`. The |
|
current image is an attribute of the current Axes. |
|
""" |
|
_api.check_isinstance((mcoll.Collection, mimage.AxesImage), im=im) |
|
if im not in self._children: |
|
raise ValueError("Argument must be an image or collection in this Axes") |
|
self._current_image = im |
|
|
|
def _gci(self): |
|
"""Helper for `~matplotlib.pyplot.gci`; do not use elsewhere.""" |
|
return self._current_image |
|
|
|
def has_data(self): |
|
""" |
|
Return whether any artists have been added to the Axes. |
|
|
|
This should not be used to determine whether the *dataLim* |
|
need to be updated, and may not actually be useful for |
|
anything. |
|
""" |
|
return any(isinstance(a, (mcoll.Collection, mimage.AxesImage, |
|
mlines.Line2D, mpatches.Patch)) |
|
for a in self._children) |
|
|
|
def add_artist(self, a): |
|
""" |
|
Add an `.Artist` to the Axes; return the artist. |
|
|
|
Use `add_artist` only for artists for which there is no dedicated |
|
"add" method; and if necessary, use a method such as `update_datalim` |
|
to manually update the dataLim if the artist is to be included in |
|
autoscaling. |
|
|
|
If no ``transform`` has been specified when creating the artist (e.g. |
|
``artist.get_transform() == None``) then the transform is set to |
|
``ax.transData``. |
|
""" |
|
a.axes = self |
|
self._children.append(a) |
|
a._remove_method = self._children.remove |
|
self._set_artist_props(a) |
|
if a.get_clip_path() is None: |
|
a.set_clip_path(self.patch) |
|
self.stale = True |
|
return a |
|
|
|
def add_child_axes(self, ax): |
|
""" |
|
Add an `.AxesBase` to the Axes' children; return the child Axes. |
|
|
|
This is the lowlevel version. See `.axes.Axes.inset_axes`. |
|
""" |
|
|
|
|
|
|
|
|
|
ax._axes = self |
|
ax.stale_callback = martist._stale_axes_callback |
|
|
|
self.child_axes.append(ax) |
|
ax._remove_method = functools.partial( |
|
self.figure._remove_axes, owners=[self.child_axes]) |
|
self.stale = True |
|
return ax |
|
|
|
def add_collection(self, collection, autolim=True): |
|
""" |
|
Add a `.Collection` to the Axes; return the collection. |
|
""" |
|
_api.check_isinstance(mcoll.Collection, collection=collection) |
|
if not collection.get_label(): |
|
collection.set_label(f'_child{len(self._children)}') |
|
self._children.append(collection) |
|
collection._remove_method = self._children.remove |
|
self._set_artist_props(collection) |
|
|
|
if collection.get_clip_path() is None: |
|
collection.set_clip_path(self.patch) |
|
|
|
if autolim: |
|
|
|
|
|
self._unstale_viewLim() |
|
datalim = collection.get_datalim(self.transData) |
|
points = datalim.get_points() |
|
if not np.isinf(datalim.minpos).all(): |
|
|
|
|
|
|
|
|
|
|
|
points = np.concatenate([points, [datalim.minpos]]) |
|
self.update_datalim(points) |
|
|
|
self.stale = True |
|
return collection |
|
|
|
def add_image(self, image): |
|
""" |
|
Add an `.AxesImage` to the Axes; return the image. |
|
""" |
|
_api.check_isinstance(mimage.AxesImage, image=image) |
|
self._set_artist_props(image) |
|
if not image.get_label(): |
|
image.set_label(f'_child{len(self._children)}') |
|
self._children.append(image) |
|
image._remove_method = self._children.remove |
|
self.stale = True |
|
return image |
|
|
|
def _update_image_limits(self, image): |
|
xmin, xmax, ymin, ymax = image.get_extent() |
|
self.axes.update_datalim(((xmin, ymin), (xmax, ymax))) |
|
|
|
def add_line(self, line): |
|
""" |
|
Add a `.Line2D` to the Axes; return the line. |
|
""" |
|
_api.check_isinstance(mlines.Line2D, line=line) |
|
self._set_artist_props(line) |
|
if line.get_clip_path() is None: |
|
line.set_clip_path(self.patch) |
|
|
|
self._update_line_limits(line) |
|
if not line.get_label(): |
|
line.set_label(f'_child{len(self._children)}') |
|
self._children.append(line) |
|
line._remove_method = self._children.remove |
|
self.stale = True |
|
return line |
|
|
|
def _add_text(self, txt): |
|
""" |
|
Add a `.Text` to the Axes; return the text. |
|
""" |
|
_api.check_isinstance(mtext.Text, txt=txt) |
|
self._set_artist_props(txt) |
|
self._children.append(txt) |
|
txt._remove_method = self._children.remove |
|
self.stale = True |
|
return txt |
|
|
|
def _update_line_limits(self, line): |
|
""" |
|
Figures out the data limit of the given line, updating self.dataLim. |
|
""" |
|
path = line.get_path() |
|
if path.vertices.size == 0: |
|
return |
|
|
|
line_trf = line.get_transform() |
|
|
|
if line_trf == self.transData: |
|
data_path = path |
|
elif any(line_trf.contains_branch_seperately(self.transData)): |
|
|
|
trf_to_data = line_trf - self.transData |
|
|
|
|
|
|
|
if self.transData.is_affine: |
|
line_trans_path = line._get_transformed_path() |
|
na_path, _ = line_trans_path.get_transformed_path_and_affine() |
|
data_path = trf_to_data.transform_path_affine(na_path) |
|
else: |
|
data_path = trf_to_data.transform_path(path) |
|
else: |
|
|
|
|
|
|
|
|
|
|
|
data_path = path |
|
|
|
if not data_path.vertices.size: |
|
return |
|
|
|
updatex, updatey = line_trf.contains_branch_seperately(self.transData) |
|
if self.name != "rectilinear": |
|
|
|
|
|
if updatex and line_trf == self.get_yaxis_transform(): |
|
updatex = False |
|
if updatey and line_trf == self.get_xaxis_transform(): |
|
updatey = False |
|
self.dataLim.update_from_path(data_path, |
|
self.ignore_existing_data_limits, |
|
updatex=updatex, updatey=updatey) |
|
self.ignore_existing_data_limits = False |
|
|
|
def add_patch(self, p): |
|
""" |
|
Add a `.Patch` to the Axes; return the patch. |
|
""" |
|
_api.check_isinstance(mpatches.Patch, p=p) |
|
self._set_artist_props(p) |
|
if p.get_clip_path() is None: |
|
p.set_clip_path(self.patch) |
|
self._update_patch_limits(p) |
|
self._children.append(p) |
|
p._remove_method = self._children.remove |
|
return p |
|
|
|
def _update_patch_limits(self, patch): |
|
"""Update the data limits for the given patch.""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isinstance(patch, mpatches.Rectangle) and |
|
((not patch.get_width()) and (not patch.get_height()))): |
|
return |
|
p = patch.get_path() |
|
|
|
|
|
vertices = [] |
|
for curve, code in p.iter_bezier(simplify=False): |
|
|
|
_, dzeros = curve.axis_aligned_extrema() |
|
|
|
vertices.append(curve([0, *dzeros, 1])) |
|
|
|
if len(vertices): |
|
vertices = np.vstack(vertices) |
|
|
|
patch_trf = patch.get_transform() |
|
updatex, updatey = patch_trf.contains_branch_seperately(self.transData) |
|
if not (updatex or updatey): |
|
return |
|
if self.name != "rectilinear": |
|
|
|
if updatex and patch_trf == self.get_yaxis_transform(): |
|
updatex = False |
|
if updatey and patch_trf == self.get_xaxis_transform(): |
|
updatey = False |
|
trf_to_data = patch_trf - self.transData |
|
xys = trf_to_data.transform(vertices) |
|
self.update_datalim(xys, updatex=updatex, updatey=updatey) |
|
|
|
def add_table(self, tab): |
|
""" |
|
Add a `.Table` to the Axes; return the table. |
|
""" |
|
_api.check_isinstance(mtable.Table, tab=tab) |
|
self._set_artist_props(tab) |
|
self._children.append(tab) |
|
if tab.get_clip_path() is None: |
|
tab.set_clip_path(self.patch) |
|
tab._remove_method = self._children.remove |
|
return tab |
|
|
|
def add_container(self, container): |
|
""" |
|
Add a `.Container` to the Axes' containers; return the container. |
|
""" |
|
label = container.get_label() |
|
if not label: |
|
container.set_label('_container%d' % len(self.containers)) |
|
self.containers.append(container) |
|
container._remove_method = self.containers.remove |
|
return container |
|
|
|
def _unit_change_handler(self, axis_name, event=None): |
|
""" |
|
Process axis units changes: requests updates to data and view limits. |
|
""" |
|
if event is None: |
|
return functools.partial( |
|
self._unit_change_handler, axis_name, event=object()) |
|
_api.check_in_list(self._axis_map, axis_name=axis_name) |
|
for line in self.lines: |
|
line.recache_always() |
|
self.relim() |
|
self._request_autoscale_view(axis_name) |
|
|
|
def relim(self, visible_only=False): |
|
""" |
|
Recompute the data limits based on current artists. |
|
|
|
At present, `.Collection` instances are not supported. |
|
|
|
Parameters |
|
---------- |
|
visible_only : bool, default: False |
|
Whether to exclude invisible artists. |
|
""" |
|
|
|
|
|
self.dataLim.ignore(True) |
|
self.dataLim.set_points(mtransforms.Bbox.null().get_points()) |
|
self.ignore_existing_data_limits = True |
|
|
|
for artist in self._children: |
|
if not visible_only or artist.get_visible(): |
|
if isinstance(artist, mlines.Line2D): |
|
self._update_line_limits(artist) |
|
elif isinstance(artist, mpatches.Patch): |
|
self._update_patch_limits(artist) |
|
elif isinstance(artist, mimage.AxesImage): |
|
self._update_image_limits(artist) |
|
|
|
def update_datalim(self, xys, updatex=True, updatey=True): |
|
""" |
|
Extend the `~.Axes.dataLim` Bbox to include the given points. |
|
|
|
If no data is set currently, the Bbox will ignore its limits and set |
|
the bound to be the bounds of the xydata (*xys*). Otherwise, it will |
|
compute the bounds of the union of its current data and the data in |
|
*xys*. |
|
|
|
Parameters |
|
---------- |
|
xys : 2D array-like |
|
The points to include in the data limits Bbox. This can be either |
|
a list of (x, y) tuples or a (N, 2) array. |
|
|
|
updatex, updatey : bool, default: True |
|
Whether to update the x/y limits. |
|
""" |
|
xys = np.asarray(xys) |
|
if not np.any(np.isfinite(xys)): |
|
return |
|
self.dataLim.update_from_data_xy(xys, self.ignore_existing_data_limits, |
|
updatex=updatex, updatey=updatey) |
|
self.ignore_existing_data_limits = False |
|
|
|
def _process_unit_info(self, datasets=None, kwargs=None, *, convert=True): |
|
""" |
|
Set axis units based on *datasets* and *kwargs*, and optionally apply |
|
unit conversions to *datasets*. |
|
|
|
Parameters |
|
---------- |
|
datasets : list |
|
List of (axis_name, dataset) pairs (where the axis name is defined |
|
as in `._axis_map`). Individual datasets can also be None |
|
(which gets passed through). |
|
kwargs : dict |
|
Other parameters from which unit info (i.e., the *xunits*, |
|
*yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for |
|
polar) entries) is popped, if present. Note that this dict is |
|
mutated in-place! |
|
convert : bool, default: True |
|
Whether to return the original datasets or the converted ones. |
|
|
|
Returns |
|
------- |
|
list |
|
Either the original datasets if *convert* is False, or the |
|
converted ones if *convert* is True (the default). |
|
""" |
|
|
|
|
|
|
|
|
|
datasets = datasets or [] |
|
kwargs = kwargs or {} |
|
axis_map = self._axis_map |
|
for axis_name, data in datasets: |
|
try: |
|
axis = axis_map[axis_name] |
|
except KeyError: |
|
raise ValueError(f"Invalid axis name: {axis_name!r}") from None |
|
|
|
if axis is not None and data is not None and not axis.have_units(): |
|
axis.update_units(data) |
|
for axis_name, axis in axis_map.items(): |
|
|
|
if axis is None: |
|
continue |
|
|
|
units = kwargs.pop(f"{axis_name}units", axis.units) |
|
if self.name == "polar": |
|
|
|
polar_units = {"x": "thetaunits", "y": "runits"} |
|
units = kwargs.pop(polar_units[axis_name], units) |
|
if units != axis.units and units is not None: |
|
axis.set_units(units) |
|
|
|
|
|
for dataset_axis_name, data in datasets: |
|
if dataset_axis_name == axis_name and data is not None: |
|
axis.update_units(data) |
|
return [axis_map[axis_name].convert_units(data) |
|
if convert and data is not None else data |
|
for axis_name, data in datasets] |
|
|
|
def in_axes(self, mouseevent): |
|
""" |
|
Return whether the given event (in display coords) is in the Axes. |
|
""" |
|
return self.patch.contains(mouseevent)[0] |
|
|
|
get_autoscalex_on = _axis_method_wrapper("xaxis", "_get_autoscale_on") |
|
get_autoscaley_on = _axis_method_wrapper("yaxis", "_get_autoscale_on") |
|
set_autoscalex_on = _axis_method_wrapper("xaxis", "_set_autoscale_on") |
|
set_autoscaley_on = _axis_method_wrapper("yaxis", "_set_autoscale_on") |
|
|
|
def get_autoscale_on(self): |
|
"""Return True if each axis is autoscaled, False otherwise.""" |
|
return all(axis._get_autoscale_on() |
|
for axis in self._axis_map.values()) |
|
|
|
def set_autoscale_on(self, b): |
|
""" |
|
Set whether autoscaling is applied to each axis on the next draw or |
|
call to `.Axes.autoscale_view`. |
|
|
|
Parameters |
|
---------- |
|
b : bool |
|
""" |
|
for axis in self._axis_map.values(): |
|
axis._set_autoscale_on(b) |
|
|
|
@property |
|
def use_sticky_edges(self): |
|
""" |
|
When autoscaling, whether to obey all `Artist.sticky_edges`. |
|
|
|
Default is ``True``. |
|
|
|
Setting this to ``False`` ensures that the specified margins |
|
will be applied, even if the plot includes an image, for |
|
example, which would otherwise force a view limit to coincide |
|
with its data limit. |
|
|
|
The changing this property does not change the plot until |
|
`autoscale` or `autoscale_view` is called. |
|
""" |
|
return self._use_sticky_edges |
|
|
|
@use_sticky_edges.setter |
|
def use_sticky_edges(self, b): |
|
self._use_sticky_edges = bool(b) |
|
|
|
|
|
def set_xmargin(self, m): |
|
""" |
|
Set padding of X data limits prior to autoscaling. |
|
|
|
*m* times the data interval will be added to each end of that interval |
|
before it is used in autoscaling. If *m* is negative, this will clip |
|
the data range instead of expanding it. |
|
|
|
For example, if your data is in the range [0, 2], a margin of 0.1 will |
|
result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range |
|
of [0.2, 1.8]. |
|
|
|
Parameters |
|
---------- |
|
m : float greater than -0.5 |
|
""" |
|
if m <= -0.5: |
|
raise ValueError("margin must be greater than -0.5") |
|
self._xmargin = m |
|
self._request_autoscale_view("x") |
|
self.stale = True |
|
|
|
def set_ymargin(self, m): |
|
""" |
|
Set padding of Y data limits prior to autoscaling. |
|
|
|
*m* times the data interval will be added to each end of that interval |
|
before it is used in autoscaling. If *m* is negative, this will clip |
|
the data range instead of expanding it. |
|
|
|
For example, if your data is in the range [0, 2], a margin of 0.1 will |
|
result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range |
|
of [0.2, 1.8]. |
|
|
|
Parameters |
|
---------- |
|
m : float greater than -0.5 |
|
""" |
|
if m <= -0.5: |
|
raise ValueError("margin must be greater than -0.5") |
|
self._ymargin = m |
|
self._request_autoscale_view("y") |
|
self.stale = True |
|
|
|
def margins(self, *margins, x=None, y=None, tight=True): |
|
""" |
|
Set or retrieve autoscaling margins. |
|
|
|
The padding added to each limit of the Axes is the *margin* |
|
times the data interval. All input parameters must be floats |
|
greater than -0.5. Passing both positional and keyword |
|
arguments is invalid and will raise a TypeError. If no |
|
arguments (positional or otherwise) are provided, the current |
|
margins will remain unchanged and simply be returned. |
|
|
|
Specifying any margin changes only the autoscaling; for example, |
|
if *xmargin* is not None, then *xmargin* times the X data |
|
interval will be added to each end of that interval before |
|
it is used in autoscaling. |
|
|
|
Parameters |
|
---------- |
|
*margins : float, optional |
|
If a single positional argument is provided, it specifies |
|
both margins of the x-axis and y-axis limits. If two |
|
positional arguments are provided, they will be interpreted |
|
as *xmargin*, *ymargin*. If setting the margin on a single |
|
axis is desired, use the keyword arguments described below. |
|
|
|
x, y : float, optional |
|
Specific margin values for the x-axis and y-axis, |
|
respectively. These cannot be used with positional |
|
arguments, but can be used individually to alter on e.g., |
|
only the y-axis. |
|
|
|
tight : bool or None, default: True |
|
The *tight* parameter is passed to `~.axes.Axes.autoscale_view`, |
|
which is executed after a margin is changed; the default |
|
here is *True*, on the assumption that when margins are |
|
specified, no additional padding to match tick marks is |
|
usually desired. Setting *tight* to *None* preserves |
|
the previous setting. |
|
|
|
Returns |
|
------- |
|
xmargin, ymargin : float |
|
|
|
Notes |
|
----- |
|
If a previously used Axes method such as :meth:`pcolor` has set |
|
:attr:`use_sticky_edges` to `True`, only the limits not set by |
|
the "sticky artists" will be modified. To force all of the |
|
margins to be set, set :attr:`use_sticky_edges` to `False` |
|
before calling :meth:`margins`. |
|
""" |
|
|
|
if margins and (x is not None or y is not None): |
|
raise TypeError('Cannot pass both positional and keyword ' |
|
'arguments for x and/or y.') |
|
elif len(margins) == 1: |
|
x = y = margins[0] |
|
elif len(margins) == 2: |
|
x, y = margins |
|
elif margins: |
|
raise TypeError('Must pass a single positional argument for all ' |
|
'margins, or one for each margin (x, y).') |
|
|
|
if x is None and y is None: |
|
if tight is not True: |
|
_api.warn_external(f'ignoring tight={tight!r} in get mode') |
|
return self._xmargin, self._ymargin |
|
|
|
if tight is not None: |
|
self._tight = tight |
|
if x is not None: |
|
self.set_xmargin(x) |
|
if y is not None: |
|
self.set_ymargin(y) |
|
|
|
def set_rasterization_zorder(self, z): |
|
""" |
|
Set the zorder threshold for rasterization for vector graphics output. |
|
|
|
All artists with a zorder below the given value will be rasterized if |
|
they support rasterization. |
|
|
|
This setting is ignored for pixel-based output. |
|
|
|
See also :doc:`/gallery/misc/rasterization_demo`. |
|
|
|
Parameters |
|
---------- |
|
z : float or None |
|
The zorder below which artists are rasterized. |
|
If ``None`` rasterization based on zorder is deactivated. |
|
""" |
|
self._rasterization_zorder = z |
|
self.stale = True |
|
|
|
def get_rasterization_zorder(self): |
|
"""Return the zorder value below which artists will be rasterized.""" |
|
return self._rasterization_zorder |
|
|
|
def autoscale(self, enable=True, axis='both', tight=None): |
|
""" |
|
Autoscale the axis view to the data (toggle). |
|
|
|
Convenience method for simple axis view autoscaling. |
|
It turns autoscaling on or off, and then, |
|
if autoscaling for either axis is on, it performs |
|
the autoscaling on the specified axis or Axes. |
|
|
|
Parameters |
|
---------- |
|
enable : bool or None, default: True |
|
True turns autoscaling on, False turns it off. |
|
None leaves the autoscaling state unchanged. |
|
axis : {'both', 'x', 'y'}, default: 'both' |
|
The axis on which to operate. (For 3D Axes, *axis* can also be set |
|
to 'z', and 'both' refers to all three axes.) |
|
tight : bool or None, default: None |
|
If True, first set the margins to zero. Then, this argument is |
|
forwarded to `~.axes.Axes.autoscale_view` (regardless of |
|
its value); see the description of its behavior there. |
|
""" |
|
if enable is None: |
|
scalex = True |
|
scaley = True |
|
else: |
|
if axis in ['x', 'both']: |
|
self.set_autoscalex_on(bool(enable)) |
|
scalex = self.get_autoscalex_on() |
|
else: |
|
scalex = False |
|
if axis in ['y', 'both']: |
|
self.set_autoscaley_on(bool(enable)) |
|
scaley = self.get_autoscaley_on() |
|
else: |
|
scaley = False |
|
if tight and scalex: |
|
self._xmargin = 0 |
|
if tight and scaley: |
|
self._ymargin = 0 |
|
if scalex: |
|
self._request_autoscale_view("x", tight=tight) |
|
if scaley: |
|
self._request_autoscale_view("y", tight=tight) |
|
|
|
def autoscale_view(self, tight=None, scalex=True, scaley=True): |
|
""" |
|
Autoscale the view limits using the data limits. |
|
|
|
Parameters |
|
---------- |
|
tight : bool or None |
|
If *True*, only expand the axis limits using the margins. Note |
|
that unlike for `autoscale`, ``tight=True`` does *not* set the |
|
margins to zero. |
|
|
|
If *False* and :rc:`axes.autolimit_mode` is 'round_numbers', then |
|
after expansion by the margins, further expand the axis limits |
|
using the axis major locator. |
|
|
|
If None (the default), reuse the value set in the previous call to |
|
`autoscale_view` (the initial value is False, but the default style |
|
sets :rc:`axes.autolimit_mode` to 'data', in which case this |
|
behaves like True). |
|
|
|
scalex : bool, default: True |
|
Whether to autoscale the x-axis. |
|
|
|
scaley : bool, default: True |
|
Whether to autoscale the y-axis. |
|
|
|
Notes |
|
----- |
|
The autoscaling preserves any preexisting axis direction reversal. |
|
|
|
The data limits are not updated automatically when artist data are |
|
changed after the artist has been added to an Axes instance. In that |
|
case, use :meth:`matplotlib.axes.Axes.relim` prior to calling |
|
autoscale_view. |
|
|
|
If the views of the Axes are fixed, e.g. via `set_xlim`, they will |
|
not be changed by autoscale_view(). |
|
See :meth:`matplotlib.axes.Axes.autoscale` for an alternative. |
|
""" |
|
if tight is not None: |
|
self._tight = bool(tight) |
|
|
|
x_stickies = y_stickies = np.array([]) |
|
if self.use_sticky_edges: |
|
if self._xmargin and scalex and self.get_autoscalex_on(): |
|
x_stickies = np.sort(np.concatenate([ |
|
artist.sticky_edges.x |
|
for ax in self._shared_axes["x"].get_siblings(self) |
|
for artist in ax.get_children()])) |
|
if self._ymargin and scaley and self.get_autoscaley_on(): |
|
y_stickies = np.sort(np.concatenate([ |
|
artist.sticky_edges.y |
|
for ax in self._shared_axes["y"].get_siblings(self) |
|
for artist in ax.get_children()])) |
|
if self.get_xscale() == 'log': |
|
x_stickies = x_stickies[x_stickies > 0] |
|
if self.get_yscale() == 'log': |
|
y_stickies = y_stickies[y_stickies > 0] |
|
|
|
def handle_single_axis( |
|
scale, shared_axes, name, axis, margin, stickies, set_bound): |
|
|
|
if not (scale and axis._get_autoscale_on()): |
|
return |
|
|
|
shared = shared_axes.get_siblings(self) |
|
|
|
|
|
values = [val for ax in shared |
|
for val in getattr(ax.dataLim, f"interval{name}") |
|
if np.isfinite(val)] |
|
if values: |
|
x0, x1 = (min(values), max(values)) |
|
elif getattr(self._viewLim, f"mutated{name}")(): |
|
|
|
|
|
return |
|
else: |
|
x0, x1 = (-np.inf, np.inf) |
|
|
|
locator = axis.get_major_locator() |
|
x0, x1 = locator.nonsingular(x0, x1) |
|
|
|
minimum_minpos = min( |
|
getattr(ax.dataLim, f"minpos{name}") for ax in shared) |
|
|
|
|
|
|
|
|
|
|
|
|
|
tol = 1e-5 * max(abs(x0), abs(x1), abs(x1 - x0)) |
|
|
|
i0 = stickies.searchsorted(x0 + tol) - 1 |
|
x0bound = stickies[i0] if i0 != -1 else None |
|
|
|
i1 = stickies.searchsorted(x1 - tol) |
|
x1bound = stickies[i1] if i1 != len(stickies) else None |
|
|
|
|
|
|
|
transform = axis.get_transform() |
|
inverse_trans = transform.inverted() |
|
x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minimum_minpos) |
|
x0t, x1t = transform.transform([x0, x1]) |
|
delta = (x1t - x0t) * margin |
|
if not np.isfinite(delta): |
|
delta = 0 |
|
x0, x1 = inverse_trans.transform([x0t - delta, x1t + delta]) |
|
|
|
|
|
if x0bound is not None: |
|
x0 = max(x0, x0bound) |
|
if x1bound is not None: |
|
x1 = min(x1, x1bound) |
|
|
|
if not self._tight: |
|
x0, x1 = locator.view_limits(x0, x1) |
|
set_bound(x0, x1) |
|
|
|
|
|
handle_single_axis( |
|
scalex, self._shared_axes["x"], 'x', self.xaxis, self._xmargin, |
|
x_stickies, self.set_xbound) |
|
handle_single_axis( |
|
scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin, |
|
y_stickies, self.set_ybound) |
|
|
|
def _update_title_position(self, renderer): |
|
""" |
|
Update the title position based on the bounding box enclosing |
|
all the ticklabels and x-axis spine and xlabel... |
|
""" |
|
if self._autotitlepos is not None and not self._autotitlepos: |
|
_log.debug('title position was updated manually, not adjusting') |
|
return |
|
|
|
titles = (self.title, self._left_title, self._right_title) |
|
|
|
|
|
axs = self._twinned_axes.get_siblings(self) + self.child_axes |
|
for ax in self.child_axes: |
|
locator = ax.get_axes_locator() |
|
ax.apply_aspect(locator(self, renderer) if locator else None) |
|
|
|
for title in titles: |
|
x, _ = title.get_position() |
|
|
|
title.set_position((x, 1.0)) |
|
top = -np.inf |
|
for ax in axs: |
|
bb = None |
|
if (ax.xaxis.get_ticks_position() in ['top', 'unknown'] |
|
or ax.xaxis.get_label_position() == 'top'): |
|
bb = ax.xaxis.get_tightbbox(renderer) |
|
if bb is None: |
|
if 'outline' in ax.spines: |
|
|
|
bb = ax.spines['outline'].get_window_extent() |
|
else: |
|
bb = ax.get_window_extent(renderer) |
|
top = max(top, bb.ymax) |
|
if title.get_text(): |
|
ax.yaxis.get_tightbbox(renderer) |
|
if ax.yaxis.offsetText.get_text(): |
|
bb = ax.yaxis.offsetText.get_tightbbox(renderer) |
|
if bb.intersection(title.get_tightbbox(renderer), bb): |
|
top = bb.ymax |
|
if top < 0: |
|
|
|
|
|
_log.debug('top of Axes not in the figure, so title not moved') |
|
return |
|
if title.get_window_extent(renderer).ymin < top: |
|
_, y = self.transAxes.inverted().transform((0, top)) |
|
title.set_position((x, y)) |
|
|
|
|
|
if title.get_window_extent(renderer).ymin < top: |
|
_, y = self.transAxes.inverted().transform( |
|
(0., 2 * top - title.get_window_extent(renderer).ymin)) |
|
title.set_position((x, y)) |
|
|
|
ymax = max(title.get_position()[1] for title in titles) |
|
for title in titles: |
|
|
|
x, _ = title.get_position() |
|
title.set_position((x, ymax)) |
|
|
|
|
|
@martist.allow_rasterization |
|
def draw(self, renderer): |
|
|
|
if renderer is None: |
|
raise RuntimeError('No renderer defined') |
|
if not self.get_visible(): |
|
return |
|
self._unstale_viewLim() |
|
|
|
renderer.open_group('axes', gid=self.get_gid()) |
|
|
|
|
|
self._stale = True |
|
|
|
|
|
locator = self.get_axes_locator() |
|
self.apply_aspect(locator(self, renderer) if locator else None) |
|
|
|
artists = self.get_children() |
|
artists.remove(self.patch) |
|
|
|
|
|
|
|
|
|
|
|
if not (self.axison and self._frameon): |
|
for spine in self.spines.values(): |
|
artists.remove(spine) |
|
|
|
self._update_title_position(renderer) |
|
|
|
if not self.axison: |
|
for _axis in self._axis_map.values(): |
|
artists.remove(_axis) |
|
|
|
if not self.figure.canvas.is_saving(): |
|
artists = [ |
|
a for a in artists |
|
if not a.get_animated() or isinstance(a, mimage.AxesImage)] |
|
artists = sorted(artists, key=attrgetter('zorder')) |
|
|
|
|
|
|
|
rasterization_zorder = self._rasterization_zorder |
|
|
|
if (rasterization_zorder is not None and |
|
artists and artists[0].zorder < rasterization_zorder): |
|
split_index = np.searchsorted( |
|
[art.zorder for art in artists], |
|
rasterization_zorder, side='right' |
|
) |
|
artists_rasterized = artists[:split_index] |
|
artists = artists[split_index:] |
|
else: |
|
artists_rasterized = [] |
|
|
|
if self.axison and self._frameon: |
|
if artists_rasterized: |
|
artists_rasterized = [self.patch] + artists_rasterized |
|
else: |
|
artists = [self.patch] + artists |
|
|
|
if artists_rasterized: |
|
_draw_rasterized(self.figure, artists_rasterized, renderer) |
|
|
|
mimage._draw_list_compositing_images( |
|
renderer, self, artists, self.figure.suppressComposite) |
|
|
|
renderer.close_group('axes') |
|
self.stale = False |
|
|
|
def draw_artist(self, a): |
|
""" |
|
Efficiently redraw a single artist. |
|
""" |
|
a.draw(self.figure.canvas.get_renderer()) |
|
|
|
def redraw_in_frame(self): |
|
""" |
|
Efficiently redraw Axes data, but not axis ticks, labels, etc. |
|
""" |
|
with ExitStack() as stack: |
|
for artist in [*self._axis_map.values(), |
|
self.title, self._left_title, self._right_title]: |
|
stack.enter_context(artist._cm_set(visible=False)) |
|
self.draw(self.figure.canvas.get_renderer()) |
|
|
|
|
|
|
|
def get_frame_on(self): |
|
"""Get whether the Axes rectangle patch is drawn.""" |
|
return self._frameon |
|
|
|
def set_frame_on(self, b): |
|
""" |
|
Set whether the Axes rectangle patch is drawn. |
|
|
|
Parameters |
|
---------- |
|
b : bool |
|
""" |
|
self._frameon = b |
|
self.stale = True |
|
|
|
def get_axisbelow(self): |
|
""" |
|
Get whether axis ticks and gridlines are above or below most artists. |
|
|
|
Returns |
|
------- |
|
bool or 'line' |
|
|
|
See Also |
|
-------- |
|
set_axisbelow |
|
""" |
|
return self._axisbelow |
|
|
|
def set_axisbelow(self, b): |
|
""" |
|
Set whether axis ticks and gridlines are above or below most artists. |
|
|
|
This controls the zorder of the ticks and gridlines. For more |
|
information on the zorder see :doc:`/gallery/misc/zorder_demo`. |
|
|
|
Parameters |
|
---------- |
|
b : bool or 'line' |
|
Possible values: |
|
|
|
- *True* (zorder = 0.5): Ticks and gridlines are below all Artists. |
|
- 'line' (zorder = 1.5): Ticks and gridlines are above patches |
|
(e.g. rectangles, with default zorder = 1) but still below lines |
|
and markers (with their default zorder = 2). |
|
- *False* (zorder = 2.5): Ticks and gridlines are above patches |
|
and lines / markers. |
|
|
|
See Also |
|
-------- |
|
get_axisbelow |
|
""" |
|
|
|
self._axisbelow = axisbelow = validate_axisbelow(b) |
|
zorder = { |
|
True: 0.5, |
|
'line': 1.5, |
|
False: 2.5, |
|
}[axisbelow] |
|
for axis in self._axis_map.values(): |
|
axis.set_zorder(zorder) |
|
self.stale = True |
|
|
|
@_docstring.dedent_interpd |
|
def grid(self, visible=None, which='major', axis='both', **kwargs): |
|
""" |
|
Configure the grid lines. |
|
|
|
Parameters |
|
---------- |
|
visible : bool or None, optional |
|
Whether to show the grid lines. If any *kwargs* are supplied, it |
|
is assumed you want the grid on and *visible* will be set to True. |
|
|
|
If *visible* is *None* and there are no *kwargs*, this toggles the |
|
visibility of the lines. |
|
|
|
which : {'major', 'minor', 'both'}, optional |
|
The grid lines to apply the changes on. |
|
|
|
axis : {'both', 'x', 'y'}, optional |
|
The axis to apply the changes on. |
|
|
|
**kwargs : `~matplotlib.lines.Line2D` properties |
|
Define the line properties of the grid, e.g.:: |
|
|
|
grid(color='r', linestyle='-', linewidth=2) |
|
|
|
Valid keyword arguments are: |
|
|
|
%(Line2D:kwdoc)s |
|
|
|
Notes |
|
----- |
|
The axis is drawn as a unit, so the effective zorder for drawing the |
|
grid is determined by the zorder of each axis, not by the zorder of the |
|
`.Line2D` objects comprising the grid. Therefore, to set grid zorder, |
|
use `.set_axisbelow` or, for more control, call the |
|
`~.Artist.set_zorder` method of each axis. |
|
""" |
|
_api.check_in_list(['x', 'y', 'both'], axis=axis) |
|
if axis in ['x', 'both']: |
|
self.xaxis.grid(visible, which=which, **kwargs) |
|
if axis in ['y', 'both']: |
|
self.yaxis.grid(visible, which=which, **kwargs) |
|
|
|
def ticklabel_format(self, *, axis='both', style='', scilimits=None, |
|
useOffset=None, useLocale=None, useMathText=None): |
|
r""" |
|
Configure the `.ScalarFormatter` used by default for linear Axes. |
|
|
|
If a parameter is not set, the corresponding property of the formatter |
|
is left unchanged. |
|
|
|
Parameters |
|
---------- |
|
axis : {'x', 'y', 'both'}, default: 'both' |
|
The axis to configure. Only major ticks are affected. |
|
|
|
style : {'sci', 'scientific', 'plain'} |
|
Whether to use scientific notation. |
|
The formatter default is to use scientific notation. |
|
|
|
scilimits : pair of ints (m, n) |
|
Scientific notation is used only for numbers outside the range |
|
10\ :sup:`m` to 10\ :sup:`n` (and only if the formatter is |
|
configured to use scientific notation at all). Use (0, 0) to |
|
include all numbers. Use (m, m) where m != 0 to fix the order of |
|
magnitude to 10\ :sup:`m`. |
|
The formatter default is :rc:`axes.formatter.limits`. |
|
|
|
useOffset : bool or float |
|
If True, the offset is calculated as needed. |
|
If False, no offset is used. |
|
If a numeric value, it sets the offset. |
|
The formatter default is :rc:`axes.formatter.useoffset`. |
|
|
|
useLocale : bool |
|
Whether to format the number using the current locale or using the |
|
C (English) locale. This affects e.g. the decimal separator. The |
|
formatter default is :rc:`axes.formatter.use_locale`. |
|
|
|
useMathText : bool |
|
Render the offset and scientific notation in mathtext. |
|
The formatter default is :rc:`axes.formatter.use_mathtext`. |
|
|
|
Raises |
|
------ |
|
AttributeError |
|
If the current formatter is not a `.ScalarFormatter`. |
|
""" |
|
style = style.lower() |
|
axis = axis.lower() |
|
if scilimits is not None: |
|
try: |
|
m, n = scilimits |
|
m + n + 1 |
|
except (ValueError, TypeError) as err: |
|
raise ValueError("scilimits must be a sequence of 2 integers" |
|
) from err |
|
STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None} |
|
is_sci_style = _api.check_getitem(STYLES, style=style) |
|
axis_map = {**{k: [v] for k, v in self._axis_map.items()}, |
|
'both': list(self._axis_map.values())} |
|
axises = _api.check_getitem(axis_map, axis=axis) |
|
try: |
|
for axis in axises: |
|
if is_sci_style is not None: |
|
axis.major.formatter.set_scientific(is_sci_style) |
|
if scilimits is not None: |
|
axis.major.formatter.set_powerlimits(scilimits) |
|
if useOffset is not None: |
|
axis.major.formatter.set_useOffset(useOffset) |
|
if useLocale is not None: |
|
axis.major.formatter.set_useLocale(useLocale) |
|
if useMathText is not None: |
|
axis.major.formatter.set_useMathText(useMathText) |
|
except AttributeError as err: |
|
raise AttributeError( |
|
"This method only works with the ScalarFormatter") from err |
|
|
|
def locator_params(self, axis='both', tight=None, **kwargs): |
|
""" |
|
Control behavior of major tick locators. |
|
|
|
Because the locator is involved in autoscaling, `~.Axes.autoscale_view` |
|
is called automatically after the parameters are changed. |
|
|
|
Parameters |
|
---------- |
|
axis : {'both', 'x', 'y'}, default: 'both' |
|
The axis on which to operate. (For 3D Axes, *axis* can also be |
|
set to 'z', and 'both' refers to all three axes.) |
|
tight : bool or None, optional |
|
Parameter passed to `~.Axes.autoscale_view`. |
|
Default is None, for no change. |
|
|
|
Other Parameters |
|
---------------- |
|
**kwargs |
|
Remaining keyword arguments are passed to directly to the |
|
``set_params()`` method of the locator. Supported keywords depend |
|
on the type of the locator. See for example |
|
`~.ticker.MaxNLocator.set_params` for the `.ticker.MaxNLocator` |
|
used by default for linear. |
|
|
|
Examples |
|
-------- |
|
When plotting small subplots, one might want to reduce the maximum |
|
number of ticks and use tight bounds, for example:: |
|
|
|
ax.locator_params(tight=True, nbins=4) |
|
|
|
""" |
|
_api.check_in_list([*self._axis_names, "both"], axis=axis) |
|
for name in self._axis_names: |
|
if axis in [name, "both"]: |
|
loc = self._axis_map[name].get_major_locator() |
|
loc.set_params(**kwargs) |
|
self._request_autoscale_view(name, tight=tight) |
|
self.stale = True |
|
|
|
def tick_params(self, axis='both', **kwargs): |
|
""" |
|
Change the appearance of ticks, tick labels, and gridlines. |
|
|
|
Tick properties that are not explicitly set using the keyword |
|
arguments remain unchanged unless *reset* is True. For the current |
|
style settings, see `.Axis.get_tick_params`. |
|
|
|
Parameters |
|
---------- |
|
axis : {'x', 'y', 'both'}, default: 'both' |
|
The axis to which the parameters are applied. |
|
which : {'major', 'minor', 'both'}, default: 'major' |
|
The group of ticks to which the parameters are applied. |
|
reset : bool, default: False |
|
Whether to reset the ticks to defaults before updating them. |
|
|
|
Other Parameters |
|
---------------- |
|
direction : {'in', 'out', 'inout'} |
|
Puts ticks inside the Axes, outside the Axes, or both. |
|
length : float |
|
Tick length in points. |
|
width : float |
|
Tick width in points. |
|
color : color |
|
Tick color. |
|
pad : float |
|
Distance in points between tick and label. |
|
labelsize : float or str |
|
Tick label font size in points or as a string (e.g., 'large'). |
|
labelcolor : color |
|
Tick label color. |
|
labelfontfamily : str |
|
Tick label font. |
|
colors : color |
|
Tick color and label color. |
|
zorder : float |
|
Tick and label zorder. |
|
bottom, top, left, right : bool |
|
Whether to draw the respective ticks. |
|
labelbottom, labeltop, labelleft, labelright : bool |
|
Whether to draw the respective tick labels. |
|
labelrotation : float |
|
Tick label rotation |
|
grid_color : color |
|
Gridline color. |
|
grid_alpha : float |
|
Transparency of gridlines: 0 (transparent) to 1 (opaque). |
|
grid_linewidth : float |
|
Width of gridlines in points. |
|
grid_linestyle : str |
|
Any valid `.Line2D` line style spec. |
|
|
|
Examples |
|
-------- |
|
:: |
|
|
|
ax.tick_params(direction='out', length=6, width=2, colors='r', |
|
grid_color='r', grid_alpha=0.5) |
|
|
|
This will make all major ticks be red, pointing out of the box, |
|
and with dimensions 6 points by 2 points. Tick labels will |
|
also be red. Gridlines will be red and translucent. |
|
|
|
""" |
|
_api.check_in_list(['x', 'y', 'both'], axis=axis) |
|
if axis in ['x', 'both']: |
|
xkw = dict(kwargs) |
|
xkw.pop('left', None) |
|
xkw.pop('right', None) |
|
xkw.pop('labelleft', None) |
|
xkw.pop('labelright', None) |
|
self.xaxis.set_tick_params(**xkw) |
|
if axis in ['y', 'both']: |
|
ykw = dict(kwargs) |
|
ykw.pop('top', None) |
|
ykw.pop('bottom', None) |
|
ykw.pop('labeltop', None) |
|
ykw.pop('labelbottom', None) |
|
self.yaxis.set_tick_params(**ykw) |
|
|
|
def set_axis_off(self): |
|
""" |
|
Hide all visual components of the x- and y-axis. |
|
|
|
This sets a flag to suppress drawing of all axis decorations, i.e. |
|
axis labels, axis spines, and the axis tick component (tick markers, |
|
tick labels, and grid lines). Individual visibility settings of these |
|
components are ignored as long as `set_axis_off()` is in effect. |
|
""" |
|
self.axison = False |
|
self.stale = True |
|
|
|
def set_axis_on(self): |
|
""" |
|
Do not hide all visual components of the x- and y-axis. |
|
|
|
This reverts the effect of a prior `.set_axis_off()` call. Whether the |
|
individual axis decorations are drawn is controlled by their respective |
|
visibility settings. |
|
|
|
This is on by default. |
|
""" |
|
self.axison = True |
|
self.stale = True |
|
|
|
|
|
|
|
def get_xlabel(self): |
|
""" |
|
Get the xlabel text string. |
|
""" |
|
label = self.xaxis.get_label() |
|
return label.get_text() |
|
|
|
def set_xlabel(self, xlabel, fontdict=None, labelpad=None, *, |
|
loc=None, **kwargs): |
|
""" |
|
Set the label for the x-axis. |
|
|
|
Parameters |
|
---------- |
|
xlabel : str |
|
The label text. |
|
|
|
labelpad : float, default: :rc:`axes.labelpad` |
|
Spacing in points from the Axes bounding box including ticks |
|
and tick labels. If None, the previous value is left as is. |
|
|
|
loc : {'left', 'center', 'right'}, default: :rc:`xaxis.labellocation` |
|
The label position. This is a high-level alternative for passing |
|
parameters *x* and *horizontalalignment*. |
|
|
|
Other Parameters |
|
---------------- |
|
**kwargs : `~matplotlib.text.Text` properties |
|
`.Text` properties control the appearance of the label. |
|
|
|
See Also |
|
-------- |
|
text : Documents the properties supported by `.Text`. |
|
""" |
|
if labelpad is not None: |
|
self.xaxis.labelpad = labelpad |
|
protected_kw = ['x', 'horizontalalignment', 'ha'] |
|
if {*kwargs} & {*protected_kw}: |
|
if loc is not None: |
|
raise TypeError(f"Specifying 'loc' is disallowed when any of " |
|
f"its corresponding low level keyword " |
|
f"arguments ({protected_kw}) are also " |
|
f"supplied") |
|
|
|
else: |
|
loc = (loc if loc is not None |
|
else mpl.rcParams['xaxis.labellocation']) |
|
_api.check_in_list(('left', 'center', 'right'), loc=loc) |
|
|
|
x = { |
|
'left': 0, |
|
'center': 0.5, |
|
'right': 1, |
|
}[loc] |
|
kwargs.update(x=x, horizontalalignment=loc) |
|
|
|
return self.xaxis.set_label_text(xlabel, fontdict, **kwargs) |
|
|
|
def invert_xaxis(self): |
|
""" |
|
Invert the x-axis. |
|
|
|
See Also |
|
-------- |
|
xaxis_inverted |
|
get_xlim, set_xlim |
|
get_xbound, set_xbound |
|
""" |
|
self.xaxis.set_inverted(not self.xaxis.get_inverted()) |
|
|
|
xaxis_inverted = _axis_method_wrapper("xaxis", "get_inverted") |
|
|
|
def get_xbound(self): |
|
""" |
|
Return the lower and upper x-axis bounds, in increasing order. |
|
|
|
See Also |
|
-------- |
|
set_xbound |
|
get_xlim, set_xlim |
|
invert_xaxis, xaxis_inverted |
|
""" |
|
left, right = self.get_xlim() |
|
if left < right: |
|
return left, right |
|
else: |
|
return right, left |
|
|
|
def set_xbound(self, lower=None, upper=None): |
|
""" |
|
Set the lower and upper numerical bounds of the x-axis. |
|
|
|
This method will honor axis inversion regardless of parameter order. |
|
It will not change the autoscaling setting (`.get_autoscalex_on()`). |
|
|
|
Parameters |
|
---------- |
|
lower, upper : float or None |
|
The lower and upper bounds. If *None*, the respective axis bound |
|
is not modified. |
|
|
|
.. ACCEPTS: (lower: float, upper: float) |
|
|
|
See Also |
|
-------- |
|
get_xbound |
|
get_xlim, set_xlim |
|
invert_xaxis, xaxis_inverted |
|
""" |
|
if upper is None and np.iterable(lower): |
|
lower, upper = lower |
|
|
|
old_lower, old_upper = self.get_xbound() |
|
if lower is None: |
|
lower = old_lower |
|
if upper is None: |
|
upper = old_upper |
|
|
|
self.set_xlim(sorted((lower, upper), |
|
reverse=bool(self.xaxis_inverted())), |
|
auto=None) |
|
|
|
def get_xlim(self): |
|
""" |
|
Return the x-axis view limits. |
|
|
|
Returns |
|
------- |
|
left, right : (float, float) |
|
The current x-axis limits in data coordinates. |
|
|
|
See Also |
|
-------- |
|
.Axes.set_xlim |
|
.Axes.set_xbound, .Axes.get_xbound |
|
.Axes.invert_xaxis, .Axes.xaxis_inverted |
|
|
|
Notes |
|
----- |
|
The x-axis may be inverted, in which case the *left* value will |
|
be greater than the *right* value. |
|
""" |
|
return tuple(self.viewLim.intervalx) |
|
|
|
def _validate_converted_limits(self, limit, convert): |
|
""" |
|
Raise ValueError if converted limits are non-finite. |
|
|
|
Note that this function also accepts None as a limit argument. |
|
|
|
Returns |
|
------- |
|
The limit value after call to convert(), or None if limit is None. |
|
""" |
|
if limit is not None: |
|
converted_limit = convert(limit) |
|
if isinstance(converted_limit, np.ndarray): |
|
converted_limit = converted_limit.squeeze() |
|
if (isinstance(converted_limit, Real) |
|
and not np.isfinite(converted_limit)): |
|
raise ValueError("Axis limits cannot be NaN or Inf") |
|
return converted_limit |
|
|
|
def set_xlim(self, left=None, right=None, *, emit=True, auto=False, |
|
xmin=None, xmax=None): |
|
""" |
|
Set the x-axis view limits. |
|
|
|
Parameters |
|
---------- |
|
left : float, optional |
|
The left xlim in data coordinates. Passing *None* leaves the |
|
limit unchanged. |
|
|
|
The left and right xlims may also be passed as the tuple |
|
(*left*, *right*) as the first positional argument (or as |
|
the *left* keyword argument). |
|
|
|
.. ACCEPTS: (left: float, right: float) |
|
|
|
right : float, optional |
|
The right xlim in data coordinates. Passing *None* leaves the |
|
limit unchanged. |
|
|
|
emit : bool, default: True |
|
Whether to notify observers of limit change. |
|
|
|
auto : bool or None, default: False |
|
Whether to turn on autoscaling of the x-axis. True turns on, |
|
False turns off, None leaves unchanged. |
|
|
|
xmin, xmax : float, optional |
|
They are equivalent to left and right respectively, and it is an |
|
error to pass both *xmin* and *left* or *xmax* and *right*. |
|
|
|
Returns |
|
------- |
|
left, right : (float, float) |
|
The new x-axis limits in data coordinates. |
|
|
|
See Also |
|
-------- |
|
get_xlim |
|
set_xbound, get_xbound |
|
invert_xaxis, xaxis_inverted |
|
|
|
Notes |
|
----- |
|
The *left* value may be greater than the *right* value, in which |
|
case the x-axis values will decrease from left to right. |
|
|
|
Examples |
|
-------- |
|
>>> set_xlim(left, right) |
|
>>> set_xlim((left, right)) |
|
>>> left, right = set_xlim(left, right) |
|
|
|
One limit may be left unchanged. |
|
|
|
>>> set_xlim(right=right_lim) |
|
|
|
Limits may be passed in reverse order to flip the direction of |
|
the x-axis. For example, suppose *x* represents the number of |
|
years before present. The x-axis limits might be set like the |
|
following so 5000 years ago is on the left of the plot and the |
|
present is on the right. |
|
|
|
>>> set_xlim(5000, 0) |
|
""" |
|
if right is None and np.iterable(left): |
|
left, right = left |
|
if xmin is not None: |
|
if left is not None: |
|
raise TypeError("Cannot pass both 'left' and 'xmin'") |
|
left = xmin |
|
if xmax is not None: |
|
if right is not None: |
|
raise TypeError("Cannot pass both 'right' and 'xmax'") |
|
right = xmax |
|
return self.xaxis._set_lim(left, right, emit=emit, auto=auto) |
|
|
|
get_xscale = _axis_method_wrapper("xaxis", "get_scale") |
|
set_xscale = _axis_method_wrapper("xaxis", "_set_axes_scale") |
|
get_xticks = _axis_method_wrapper("xaxis", "get_ticklocs") |
|
set_xticks = _axis_method_wrapper("xaxis", "set_ticks", |
|
doc_sub={'set_ticks': 'set_xticks'}) |
|
get_xmajorticklabels = _axis_method_wrapper("xaxis", "get_majorticklabels") |
|
get_xminorticklabels = _axis_method_wrapper("xaxis", "get_minorticklabels") |
|
get_xticklabels = _axis_method_wrapper("xaxis", "get_ticklabels") |
|
set_xticklabels = _axis_method_wrapper( |
|
"xaxis", "set_ticklabels", |
|
doc_sub={"Axis.set_ticks": "Axes.set_xticks"}) |
|
|
|
def get_ylabel(self): |
|
""" |
|
Get the ylabel text string. |
|
""" |
|
label = self.yaxis.get_label() |
|
return label.get_text() |
|
|
|
def set_ylabel(self, ylabel, fontdict=None, labelpad=None, *, |
|
loc=None, **kwargs): |
|
""" |
|
Set the label for the y-axis. |
|
|
|
Parameters |
|
---------- |
|
ylabel : str |
|
The label text. |
|
|
|
labelpad : float, default: :rc:`axes.labelpad` |
|
Spacing in points from the Axes bounding box including ticks |
|
and tick labels. If None, the previous value is left as is. |
|
|
|
loc : {'bottom', 'center', 'top'}, default: :rc:`yaxis.labellocation` |
|
The label position. This is a high-level alternative for passing |
|
parameters *y* and *horizontalalignment*. |
|
|
|
Other Parameters |
|
---------------- |
|
**kwargs : `~matplotlib.text.Text` properties |
|
`.Text` properties control the appearance of the label. |
|
|
|
See Also |
|
-------- |
|
text : Documents the properties supported by `.Text`. |
|
""" |
|
if labelpad is not None: |
|
self.yaxis.labelpad = labelpad |
|
protected_kw = ['y', 'horizontalalignment', 'ha'] |
|
if {*kwargs} & {*protected_kw}: |
|
if loc is not None: |
|
raise TypeError(f"Specifying 'loc' is disallowed when any of " |
|
f"its corresponding low level keyword " |
|
f"arguments ({protected_kw}) are also " |
|
f"supplied") |
|
|
|
else: |
|
loc = (loc if loc is not None |
|
else mpl.rcParams['yaxis.labellocation']) |
|
_api.check_in_list(('bottom', 'center', 'top'), loc=loc) |
|
|
|
y, ha = { |
|
'bottom': (0, 'left'), |
|
'center': (0.5, 'center'), |
|
'top': (1, 'right') |
|
}[loc] |
|
kwargs.update(y=y, horizontalalignment=ha) |
|
|
|
return self.yaxis.set_label_text(ylabel, fontdict, **kwargs) |
|
|
|
def invert_yaxis(self): |
|
""" |
|
Invert the y-axis. |
|
|
|
See Also |
|
-------- |
|
yaxis_inverted |
|
get_ylim, set_ylim |
|
get_ybound, set_ybound |
|
""" |
|
self.yaxis.set_inverted(not self.yaxis.get_inverted()) |
|
|
|
yaxis_inverted = _axis_method_wrapper("yaxis", "get_inverted") |
|
|
|
def get_ybound(self): |
|
""" |
|
Return the lower and upper y-axis bounds, in increasing order. |
|
|
|
See Also |
|
-------- |
|
set_ybound |
|
get_ylim, set_ylim |
|
invert_yaxis, yaxis_inverted |
|
""" |
|
bottom, top = self.get_ylim() |
|
if bottom < top: |
|
return bottom, top |
|
else: |
|
return top, bottom |
|
|
|
def set_ybound(self, lower=None, upper=None): |
|
""" |
|
Set the lower and upper numerical bounds of the y-axis. |
|
|
|
This method will honor axis inversion regardless of parameter order. |
|
It will not change the autoscaling setting (`.get_autoscaley_on()`). |
|
|
|
Parameters |
|
---------- |
|
lower, upper : float or None |
|
The lower and upper bounds. If *None*, the respective axis bound |
|
is not modified. |
|
|
|
.. ACCEPTS: (lower: float, upper: float) |
|
|
|
See Also |
|
-------- |
|
get_ybound |
|
get_ylim, set_ylim |
|
invert_yaxis, yaxis_inverted |
|
""" |
|
if upper is None and np.iterable(lower): |
|
lower, upper = lower |
|
|
|
old_lower, old_upper = self.get_ybound() |
|
if lower is None: |
|
lower = old_lower |
|
if upper is None: |
|
upper = old_upper |
|
|
|
self.set_ylim(sorted((lower, upper), |
|
reverse=bool(self.yaxis_inverted())), |
|
auto=None) |
|
|
|
def get_ylim(self): |
|
""" |
|
Return the y-axis view limits. |
|
|
|
Returns |
|
------- |
|
bottom, top : (float, float) |
|
The current y-axis limits in data coordinates. |
|
|
|
See Also |
|
-------- |
|
.Axes.set_ylim |
|
.Axes.set_ybound, .Axes.get_ybound |
|
.Axes.invert_yaxis, .Axes.yaxis_inverted |
|
|
|
Notes |
|
----- |
|
The y-axis may be inverted, in which case the *bottom* value |
|
will be greater than the *top* value. |
|
""" |
|
return tuple(self.viewLim.intervaly) |
|
|
|
def set_ylim(self, bottom=None, top=None, *, emit=True, auto=False, |
|
ymin=None, ymax=None): |
|
""" |
|
Set the y-axis view limits. |
|
|
|
Parameters |
|
---------- |
|
bottom : float, optional |
|
The bottom ylim in data coordinates. Passing *None* leaves the |
|
limit unchanged. |
|
|
|
The bottom and top ylims may also be passed as the tuple |
|
(*bottom*, *top*) as the first positional argument (or as |
|
the *bottom* keyword argument). |
|
|
|
.. ACCEPTS: (bottom: float, top: float) |
|
|
|
top : float, optional |
|
The top ylim in data coordinates. Passing *None* leaves the |
|
limit unchanged. |
|
|
|
emit : bool, default: True |
|
Whether to notify observers of limit change. |
|
|
|
auto : bool or None, default: False |
|
Whether to turn on autoscaling of the y-axis. *True* turns on, |
|
*False* turns off, *None* leaves unchanged. |
|
|
|
ymin, ymax : float, optional |
|
They are equivalent to bottom and top respectively, and it is an |
|
error to pass both *ymin* and *bottom* or *ymax* and *top*. |
|
|
|
Returns |
|
------- |
|
bottom, top : (float, float) |
|
The new y-axis limits in data coordinates. |
|
|
|
See Also |
|
-------- |
|
get_ylim |
|
set_ybound, get_ybound |
|
invert_yaxis, yaxis_inverted |
|
|
|
Notes |
|
----- |
|
The *bottom* value may be greater than the *top* value, in which |
|
case the y-axis values will decrease from *bottom* to *top*. |
|
|
|
Examples |
|
-------- |
|
>>> set_ylim(bottom, top) |
|
>>> set_ylim((bottom, top)) |
|
>>> bottom, top = set_ylim(bottom, top) |
|
|
|
One limit may be left unchanged. |
|
|
|
>>> set_ylim(top=top_lim) |
|
|
|
Limits may be passed in reverse order to flip the direction of |
|
the y-axis. For example, suppose ``y`` represents depth of the |
|
ocean in m. The y-axis limits might be set like the following |
|
so 5000 m depth is at the bottom of the plot and the surface, |
|
0 m, is at the top. |
|
|
|
>>> set_ylim(5000, 0) |
|
""" |
|
if top is None and np.iterable(bottom): |
|
bottom, top = bottom |
|
if ymin is not None: |
|
if bottom is not None: |
|
raise TypeError("Cannot pass both 'bottom' and 'ymin'") |
|
bottom = ymin |
|
if ymax is not None: |
|
if top is not None: |
|
raise TypeError("Cannot pass both 'top' and 'ymax'") |
|
top = ymax |
|
return self.yaxis._set_lim(bottom, top, emit=emit, auto=auto) |
|
|
|
get_yscale = _axis_method_wrapper("yaxis", "get_scale") |
|
set_yscale = _axis_method_wrapper("yaxis", "_set_axes_scale") |
|
get_yticks = _axis_method_wrapper("yaxis", "get_ticklocs") |
|
set_yticks = _axis_method_wrapper("yaxis", "set_ticks", |
|
doc_sub={'set_ticks': 'set_yticks'}) |
|
get_ymajorticklabels = _axis_method_wrapper("yaxis", "get_majorticklabels") |
|
get_yminorticklabels = _axis_method_wrapper("yaxis", "get_minorticklabels") |
|
get_yticklabels = _axis_method_wrapper("yaxis", "get_ticklabels") |
|
set_yticklabels = _axis_method_wrapper( |
|
"yaxis", "set_ticklabels", |
|
doc_sub={"Axis.set_ticks": "Axes.set_yticks"}) |
|
|
|
xaxis_date = _axis_method_wrapper("xaxis", "axis_date") |
|
yaxis_date = _axis_method_wrapper("yaxis", "axis_date") |
|
|
|
def format_xdata(self, x): |
|
""" |
|
Return *x* formatted as an x-value. |
|
|
|
This function will use the `.fmt_xdata` attribute if it is not None, |
|
else will fall back on the xaxis major formatter. |
|
""" |
|
return (self.fmt_xdata if self.fmt_xdata is not None |
|
else self.xaxis.get_major_formatter().format_data_short)(x) |
|
|
|
def format_ydata(self, y): |
|
""" |
|
Return *y* formatted as a y-value. |
|
|
|
This function will use the `.fmt_ydata` attribute if it is not None, |
|
else will fall back on the yaxis major formatter. |
|
""" |
|
return (self.fmt_ydata if self.fmt_ydata is not None |
|
else self.yaxis.get_major_formatter().format_data_short)(y) |
|
|
|
def format_coord(self, x, y): |
|
"""Return a format string formatting the *x*, *y* coordinates.""" |
|
return "x={} y={}".format( |
|
"???" if x is None else self.format_xdata(x), |
|
"???" if y is None else self.format_ydata(y), |
|
) |
|
|
|
def minorticks_on(self): |
|
""" |
|
Display minor ticks on the Axes. |
|
|
|
Displaying minor ticks may reduce performance; you may turn them off |
|
using `minorticks_off()` if drawing speed is a problem. |
|
""" |
|
for ax in (self.xaxis, self.yaxis): |
|
scale = ax.get_scale() |
|
if scale == 'log': |
|
s = ax._scale |
|
ax.set_minor_locator(mticker.LogLocator(s.base, s.subs)) |
|
elif scale == 'symlog': |
|
s = ax._scale |
|
ax.set_minor_locator( |
|
mticker.SymmetricalLogLocator(s._transform, s.subs)) |
|
else: |
|
ax.set_minor_locator(mticker.AutoMinorLocator()) |
|
|
|
def minorticks_off(self): |
|
"""Remove minor ticks from the Axes.""" |
|
self.xaxis.set_minor_locator(mticker.NullLocator()) |
|
self.yaxis.set_minor_locator(mticker.NullLocator()) |
|
|
|
|
|
|
|
def can_zoom(self): |
|
""" |
|
Return whether this Axes supports the zoom box button functionality. |
|
""" |
|
return True |
|
|
|
def can_pan(self): |
|
""" |
|
Return whether this Axes supports any pan/zoom button functionality. |
|
""" |
|
return True |
|
|
|
def get_navigate(self): |
|
""" |
|
Get whether the Axes responds to navigation commands. |
|
""" |
|
return self._navigate |
|
|
|
def set_navigate(self, b): |
|
""" |
|
Set whether the Axes responds to navigation toolbar commands. |
|
|
|
Parameters |
|
---------- |
|
b : bool |
|
""" |
|
self._navigate = b |
|
|
|
def get_navigate_mode(self): |
|
""" |
|
Get the navigation toolbar button status: 'PAN', 'ZOOM', or None. |
|
""" |
|
return self._navigate_mode |
|
|
|
def set_navigate_mode(self, b): |
|
""" |
|
Set the navigation toolbar button status. |
|
|
|
.. warning:: |
|
This is not a user-API function. |
|
|
|
""" |
|
self._navigate_mode = b |
|
|
|
def _get_view(self): |
|
""" |
|
Save information required to reproduce the current view. |
|
|
|
This method is called before a view is changed, such as during a pan or zoom |
|
initiated by the user. It returns an opaque object that describes the current |
|
view, in a format compatible with :meth:`_set_view`. |
|
|
|
The default implementation saves the view limits and autoscaling state. |
|
Subclasses may override this as needed, as long as :meth:`_set_view` is also |
|
adjusted accordingly. |
|
""" |
|
return { |
|
"xlim": self.get_xlim(), "autoscalex_on": self.get_autoscalex_on(), |
|
"ylim": self.get_ylim(), "autoscaley_on": self.get_autoscaley_on(), |
|
} |
|
|
|
def _set_view(self, view): |
|
""" |
|
Apply a previously saved view. |
|
|
|
This method is called when restoring a view (with the return value of |
|
:meth:`_get_view` as argument), such as with the navigation buttons. |
|
|
|
Subclasses that override :meth:`_get_view` also need to override this method |
|
accordingly. |
|
""" |
|
self.set(**view) |
|
|
|
def _prepare_view_from_bbox(self, bbox, direction='in', |
|
mode=None, twinx=False, twiny=False): |
|
""" |
|
Helper function to prepare the new bounds from a bbox. |
|
|
|
This helper function returns the new x and y bounds from the zoom |
|
bbox. This a convenience method to abstract the bbox logic |
|
out of the base setter. |
|
""" |
|
if len(bbox) == 3: |
|
xp, yp, scl = bbox |
|
if scl == 0: |
|
scl = 1. |
|
if scl > 1: |
|
direction = 'in' |
|
else: |
|
direction = 'out' |
|
scl = 1/scl |
|
|
|
(xmin, ymin), (xmax, ymax) = self.transData.transform( |
|
np.transpose([self.get_xlim(), self.get_ylim()])) |
|
|
|
xwidth = xmax - xmin |
|
ywidth = ymax - ymin |
|
xcen = (xmax + xmin)*.5 |
|
ycen = (ymax + ymin)*.5 |
|
xzc = (xp*(scl - 1) + xcen)/scl |
|
yzc = (yp*(scl - 1) + ycen)/scl |
|
bbox = [xzc - xwidth/2./scl, yzc - ywidth/2./scl, |
|
xzc + xwidth/2./scl, yzc + ywidth/2./scl] |
|
elif len(bbox) != 4: |
|
|
|
_api.warn_external( |
|
"Warning in _set_view_from_bbox: bounding box is not a tuple " |
|
"of length 3 or 4. Ignoring the view change.") |
|
return |
|
|
|
|
|
xmin0, xmax0 = self.get_xbound() |
|
ymin0, ymax0 = self.get_ybound() |
|
|
|
startx, starty, stopx, stopy = bbox |
|
|
|
(startx, starty), (stopx, stopy) = self.transData.inverted().transform( |
|
[(startx, starty), (stopx, stopy)]) |
|
|
|
xmin, xmax = np.clip(sorted([startx, stopx]), xmin0, xmax0) |
|
ymin, ymax = np.clip(sorted([starty, stopy]), ymin0, ymax0) |
|
|
|
if twinx or mode == "y": |
|
xmin, xmax = xmin0, xmax0 |
|
if twiny or mode == "x": |
|
ymin, ymax = ymin0, ymax0 |
|
|
|
if direction == "in": |
|
new_xbound = xmin, xmax |
|
new_ybound = ymin, ymax |
|
|
|
elif direction == "out": |
|
x_trf = self.xaxis.get_transform() |
|
sxmin0, sxmax0, sxmin, sxmax = x_trf.transform( |
|
[xmin0, xmax0, xmin, xmax]) |
|
factor = (sxmax0 - sxmin0) / (sxmax - sxmin) |
|
|
|
|
|
sxmin1 = sxmin0 - factor * (sxmin - sxmin0) |
|
sxmax1 = sxmax0 + factor * (sxmax0 - sxmax) |
|
|
|
new_xbound = x_trf.inverted().transform([sxmin1, sxmax1]) |
|
|
|
y_trf = self.yaxis.get_transform() |
|
symin0, symax0, symin, symax = y_trf.transform( |
|
[ymin0, ymax0, ymin, ymax]) |
|
factor = (symax0 - symin0) / (symax - symin) |
|
symin1 = symin0 - factor * (symin - symin0) |
|
symax1 = symax0 + factor * (symax0 - symax) |
|
new_ybound = y_trf.inverted().transform([symin1, symax1]) |
|
|
|
return new_xbound, new_ybound |
|
|
|
def _set_view_from_bbox(self, bbox, direction='in', |
|
mode=None, twinx=False, twiny=False): |
|
""" |
|
Update view from a selection bbox. |
|
|
|
.. note:: |
|
|
|
Intended to be overridden by new projection types, but if not, the |
|
default implementation sets the view limits to the bbox directly. |
|
|
|
Parameters |
|
---------- |
|
bbox : 4-tuple or 3 tuple |
|
* If bbox is a 4 tuple, it is the selected bounding box limits, |
|
in *display* coordinates. |
|
* If bbox is a 3 tuple, it is an (xp, yp, scl) triple, where |
|
(xp, yp) is the center of zooming and scl the scale factor to |
|
zoom by. |
|
|
|
direction : str |
|
The direction to apply the bounding box. |
|
* `'in'` - The bounding box describes the view directly, i.e., |
|
it zooms in. |
|
* `'out'` - The bounding box describes the size to make the |
|
existing view, i.e., it zooms out. |
|
|
|
mode : str or None |
|
The selection mode, whether to apply the bounding box in only the |
|
`'x'` direction, `'y'` direction or both (`None`). |
|
|
|
twinx : bool |
|
Whether this axis is twinned in the *x*-direction. |
|
|
|
twiny : bool |
|
Whether this axis is twinned in the *y*-direction. |
|
""" |
|
new_xbound, new_ybound = self._prepare_view_from_bbox( |
|
bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny) |
|
if not twinx and mode != "y": |
|
self.set_xbound(new_xbound) |
|
self.set_autoscalex_on(False) |
|
if not twiny and mode != "x": |
|
self.set_ybound(new_ybound) |
|
self.set_autoscaley_on(False) |
|
|
|
def start_pan(self, x, y, button): |
|
""" |
|
Called when a pan operation has started. |
|
|
|
Parameters |
|
---------- |
|
x, y : float |
|
The mouse coordinates in display coords. |
|
button : `.MouseButton` |
|
The pressed mouse button. |
|
|
|
Notes |
|
----- |
|
This is intended to be overridden by new projection types. |
|
""" |
|
self._pan_start = types.SimpleNamespace( |
|
lim=self.viewLim.frozen(), |
|
trans=self.transData.frozen(), |
|
trans_inverse=self.transData.inverted().frozen(), |
|
bbox=self.bbox.frozen(), |
|
x=x, |
|
y=y) |
|
|
|
def end_pan(self): |
|
""" |
|
Called when a pan operation completes (when the mouse button is up.) |
|
|
|
Notes |
|
----- |
|
This is intended to be overridden by new projection types. |
|
""" |
|
del self._pan_start |
|
|
|
def _get_pan_points(self, button, key, x, y): |
|
""" |
|
Helper function to return the new points after a pan. |
|
|
|
This helper function returns the points on the axis after a pan has |
|
occurred. This is a convenience method to abstract the pan logic |
|
out of the base setter. |
|
""" |
|
def format_deltas(key, dx, dy): |
|
if key == 'control': |
|
if abs(dx) > abs(dy): |
|
dy = dx |
|
else: |
|
dx = dy |
|
elif key == 'x': |
|
dy = 0 |
|
elif key == 'y': |
|
dx = 0 |
|
elif key == 'shift': |
|
if 2 * abs(dx) < abs(dy): |
|
dx = 0 |
|
elif 2 * abs(dy) < abs(dx): |
|
dy = 0 |
|
elif abs(dx) > abs(dy): |
|
dy = dy / abs(dy) * abs(dx) |
|
else: |
|
dx = dx / abs(dx) * abs(dy) |
|
return dx, dy |
|
|
|
p = self._pan_start |
|
dx = x - p.x |
|
dy = y - p.y |
|
if dx == dy == 0: |
|
return |
|
if button == 1: |
|
dx, dy = format_deltas(key, dx, dy) |
|
result = p.bbox.translated(-dx, -dy).transformed(p.trans_inverse) |
|
elif button == 3: |
|
try: |
|
dx = -dx / self.bbox.width |
|
dy = -dy / self.bbox.height |
|
dx, dy = format_deltas(key, dx, dy) |
|
if self.get_aspect() != 'auto': |
|
dx = dy = 0.5 * (dx + dy) |
|
alpha = np.power(10.0, (dx, dy)) |
|
start = np.array([p.x, p.y]) |
|
oldpoints = p.lim.transformed(p.trans) |
|
newpoints = start + alpha * (oldpoints - start) |
|
result = (mtransforms.Bbox(newpoints) |
|
.transformed(p.trans_inverse)) |
|
except OverflowError: |
|
_api.warn_external('Overflow while panning') |
|
return |
|
else: |
|
return |
|
|
|
valid = np.isfinite(result.transformed(p.trans)) |
|
points = result.get_points().astype(object) |
|
|
|
points[~valid] = None |
|
return points |
|
|
|
def drag_pan(self, button, key, x, y): |
|
""" |
|
Called when the mouse moves during a pan operation. |
|
|
|
Parameters |
|
---------- |
|
button : `.MouseButton` |
|
The pressed mouse button. |
|
key : str or None |
|
The pressed key, if any. |
|
x, y : float |
|
The mouse coordinates in display coords. |
|
|
|
Notes |
|
----- |
|
This is intended to be overridden by new projection types. |
|
""" |
|
points = self._get_pan_points(button, key, x, y) |
|
if points is not None: |
|
self.set_xlim(points[:, 0]) |
|
self.set_ylim(points[:, 1]) |
|
|
|
def get_children(self): |
|
|
|
return [ |
|
*self._children, |
|
*self.spines.values(), |
|
*self._axis_map.values(), |
|
self.title, self._left_title, self._right_title, |
|
*self.child_axes, |
|
*([self.legend_] if self.legend_ is not None else []), |
|
self.patch, |
|
] |
|
|
|
def contains(self, mouseevent): |
|
|
|
return self.patch.contains(mouseevent) |
|
|
|
def contains_point(self, point): |
|
""" |
|
Return whether *point* (pair of pixel coordinates) is inside the Axes |
|
patch. |
|
""" |
|
return self.patch.contains_point(point, radius=1.0) |
|
|
|
def get_default_bbox_extra_artists(self): |
|
""" |
|
Return a default list of artists that are used for the bounding box |
|
calculation. |
|
|
|
Artists are excluded either by not being visible or |
|
``artist.set_in_layout(False)``. |
|
""" |
|
|
|
artists = self.get_children() |
|
|
|
for axis in self._axis_map.values(): |
|
|
|
|
|
artists.remove(axis) |
|
if not (self.axison and self._frameon): |
|
|
|
for spine in self.spines.values(): |
|
artists.remove(spine) |
|
|
|
artists.remove(self.title) |
|
artists.remove(self._left_title) |
|
artists.remove(self._right_title) |
|
|
|
|
|
|
|
|
|
noclip = (_AxesBase, maxis.Axis, |
|
offsetbox.AnnotationBbox, offsetbox.OffsetBox) |
|
return [a for a in artists if a.get_visible() and a.get_in_layout() |
|
and (isinstance(a, noclip) or not a._fully_clipped_to_axes())] |
|
|
|
@_api.make_keyword_only("3.8", "call_axes_locator") |
|
def get_tightbbox(self, renderer=None, call_axes_locator=True, |
|
bbox_extra_artists=None, *, for_layout_only=False): |
|
""" |
|
Return the tight bounding box of the Axes, including axis and their |
|
decorators (xlabel, title, etc). |
|
|
|
Artists that have ``artist.set_in_layout(False)`` are not included |
|
in the bbox. |
|
|
|
Parameters |
|
---------- |
|
renderer : `.RendererBase` subclass |
|
renderer that will be used to draw the figures (i.e. |
|
``fig.canvas.get_renderer()``) |
|
|
|
bbox_extra_artists : list of `.Artist` or ``None`` |
|
List of artists to include in the tight bounding box. If |
|
``None`` (default), then all artist children of the Axes are |
|
included in the tight bounding box. |
|
|
|
call_axes_locator : bool, default: True |
|
If *call_axes_locator* is ``False``, it does not call the |
|
``_axes_locator`` attribute, which is necessary to get the correct |
|
bounding box. ``call_axes_locator=False`` can be used if the |
|
caller is only interested in the relative size of the tightbbox |
|
compared to the Axes bbox. |
|
|
|
for_layout_only : default: False |
|
The bounding box will *not* include the x-extent of the title and |
|
the xlabel, or the y-extent of the ylabel. |
|
|
|
Returns |
|
------- |
|
`.BboxBase` |
|
Bounding box in figure pixel coordinates. |
|
|
|
See Also |
|
-------- |
|
matplotlib.axes.Axes.get_window_extent |
|
matplotlib.axis.Axis.get_tightbbox |
|
matplotlib.spines.Spine.get_window_extent |
|
""" |
|
|
|
bb = [] |
|
if renderer is None: |
|
renderer = self.figure._get_renderer() |
|
|
|
if not self.get_visible(): |
|
return None |
|
|
|
locator = self.get_axes_locator() |
|
self.apply_aspect( |
|
locator(self, renderer) if locator and call_axes_locator else None) |
|
|
|
for axis in self._axis_map.values(): |
|
if self.axison and axis.get_visible(): |
|
ba = martist._get_tightbbox_for_layout_only(axis, renderer) |
|
if ba: |
|
bb.append(ba) |
|
self._update_title_position(renderer) |
|
axbbox = self.get_window_extent(renderer) |
|
bb.append(axbbox) |
|
|
|
for title in [self.title, self._left_title, self._right_title]: |
|
if title.get_visible(): |
|
bt = title.get_window_extent(renderer) |
|
if for_layout_only and bt.width > 0: |
|
|
|
|
|
|
|
bt.x0 = (bt.x0 + bt.x1) / 2 - 0.5 |
|
bt.x1 = bt.x0 + 1.0 |
|
bb.append(bt) |
|
|
|
bbox_artists = bbox_extra_artists |
|
if bbox_artists is None: |
|
bbox_artists = self.get_default_bbox_extra_artists() |
|
|
|
for a in bbox_artists: |
|
bbox = a.get_tightbbox(renderer) |
|
if (bbox is not None |
|
and 0 < bbox.width < np.inf |
|
and 0 < bbox.height < np.inf): |
|
bb.append(bbox) |
|
return mtransforms.Bbox.union( |
|
[b for b in bb if b.width != 0 or b.height != 0]) |
|
|
|
def _make_twin_axes(self, *args, **kwargs): |
|
"""Make a twinx Axes of self. This is used for twinx and twiny.""" |
|
if 'sharex' in kwargs and 'sharey' in kwargs: |
|
|
|
|
|
if kwargs["sharex"] is not self and kwargs["sharey"] is not self: |
|
raise ValueError("Twinned Axes may share only one axis") |
|
ss = self.get_subplotspec() |
|
if ss: |
|
twin = self.figure.add_subplot(ss, *args, **kwargs) |
|
else: |
|
twin = self.figure.add_axes( |
|
self.get_position(True), *args, **kwargs, |
|
axes_locator=_TransformedBoundsLocator( |
|
[0, 0, 1, 1], self.transAxes)) |
|
self.set_adjustable('datalim') |
|
twin.set_adjustable('datalim') |
|
self._twinned_axes.join(self, twin) |
|
return twin |
|
|
|
def twinx(self): |
|
""" |
|
Create a twin Axes sharing the xaxis. |
|
|
|
Create a new Axes with an invisible x-axis and an independent |
|
y-axis positioned opposite to the original one (i.e. at right). The |
|
x-axis autoscale setting will be inherited from the original |
|
Axes. To ensure that the tick marks of both y-axes align, see |
|
`~matplotlib.ticker.LinearLocator`. |
|
|
|
Returns |
|
------- |
|
Axes |
|
The newly created Axes instance |
|
|
|
Notes |
|
----- |
|
For those who are 'picking' artists while using twinx, pick |
|
events are only called for the artists in the top-most Axes. |
|
""" |
|
ax2 = self._make_twin_axes(sharex=self) |
|
ax2.yaxis.tick_right() |
|
ax2.yaxis.set_label_position('right') |
|
ax2.yaxis.set_offset_position('right') |
|
ax2.set_autoscalex_on(self.get_autoscalex_on()) |
|
self.yaxis.tick_left() |
|
ax2.xaxis.set_visible(False) |
|
ax2.patch.set_visible(False) |
|
ax2.xaxis.units = self.xaxis.units |
|
return ax2 |
|
|
|
def twiny(self): |
|
""" |
|
Create a twin Axes sharing the yaxis. |
|
|
|
Create a new Axes with an invisible y-axis and an independent |
|
x-axis positioned opposite to the original one (i.e. at top). The |
|
y-axis autoscale setting will be inherited from the original Axes. |
|
To ensure that the tick marks of both x-axes align, see |
|
`~matplotlib.ticker.LinearLocator`. |
|
|
|
Returns |
|
------- |
|
Axes |
|
The newly created Axes instance |
|
|
|
Notes |
|
----- |
|
For those who are 'picking' artists while using twiny, pick |
|
events are only called for the artists in the top-most Axes. |
|
""" |
|
ax2 = self._make_twin_axes(sharey=self) |
|
ax2.xaxis.tick_top() |
|
ax2.xaxis.set_label_position('top') |
|
ax2.set_autoscaley_on(self.get_autoscaley_on()) |
|
self.xaxis.tick_bottom() |
|
ax2.yaxis.set_visible(False) |
|
ax2.patch.set_visible(False) |
|
ax2.yaxis.units = self.yaxis.units |
|
return ax2 |
|
|
|
def get_shared_x_axes(self): |
|
"""Return an immutable view on the shared x-axes Grouper.""" |
|
return cbook.GrouperView(self._shared_axes["x"]) |
|
|
|
def get_shared_y_axes(self): |
|
"""Return an immutable view on the shared y-axes Grouper.""" |
|
return cbook.GrouperView(self._shared_axes["y"]) |
|
|
|
def label_outer(self, remove_inner_ticks=False): |
|
""" |
|
Only show "outer" labels and tick labels. |
|
|
|
x-labels are only kept for subplots on the last row (or first row, if |
|
labels are on the top side); y-labels only for subplots on the first |
|
column (or last column, if labels are on the right side). |
|
|
|
Parameters |
|
---------- |
|
remove_inner_ticks : bool, default: False |
|
If True, remove the inner ticks as well (not only tick labels). |
|
|
|
.. versionadded:: 3.8 |
|
""" |
|
self._label_outer_xaxis(skip_non_rectangular_axes=False, |
|
remove_inner_ticks=remove_inner_ticks) |
|
self._label_outer_yaxis(skip_non_rectangular_axes=False, |
|
remove_inner_ticks=remove_inner_ticks) |
|
|
|
def _label_outer_xaxis(self, *, skip_non_rectangular_axes, |
|
remove_inner_ticks=False): |
|
|
|
if skip_non_rectangular_axes and not isinstance(self.patch, |
|
mpl.patches.Rectangle): |
|
return |
|
ss = self.get_subplotspec() |
|
if not ss: |
|
return |
|
label_position = self.xaxis.get_label_position() |
|
if not ss.is_first_row(): |
|
if label_position == "top": |
|
self.set_xlabel("") |
|
top_kw = {'top': False} if remove_inner_ticks else {} |
|
self.xaxis.set_tick_params( |
|
which="both", labeltop=False, **top_kw) |
|
if self.xaxis.offsetText.get_position()[1] == 1: |
|
self.xaxis.offsetText.set_visible(False) |
|
if not ss.is_last_row(): |
|
if label_position == "bottom": |
|
self.set_xlabel("") |
|
bottom_kw = {'bottom': False} if remove_inner_ticks else {} |
|
self.xaxis.set_tick_params( |
|
which="both", labelbottom=False, **bottom_kw) |
|
if self.xaxis.offsetText.get_position()[1] == 0: |
|
self.xaxis.offsetText.set_visible(False) |
|
|
|
def _label_outer_yaxis(self, *, skip_non_rectangular_axes, |
|
remove_inner_ticks=False): |
|
|
|
if skip_non_rectangular_axes and not isinstance(self.patch, |
|
mpl.patches.Rectangle): |
|
return |
|
ss = self.get_subplotspec() |
|
if not ss: |
|
return |
|
label_position = self.yaxis.get_label_position() |
|
if not ss.is_first_col(): |
|
if label_position == "left": |
|
self.set_ylabel("") |
|
left_kw = {'left': False} if remove_inner_ticks else {} |
|
self.yaxis.set_tick_params( |
|
which="both", labelleft=False, **left_kw) |
|
if self.yaxis.offsetText.get_position()[0] == 0: |
|
self.yaxis.offsetText.set_visible(False) |
|
if not ss.is_last_col(): |
|
if label_position == "right": |
|
self.set_ylabel("") |
|
right_kw = {'right': False} if remove_inner_ticks else {} |
|
self.yaxis.set_tick_params( |
|
which="both", labelright=False, **right_kw) |
|
if self.yaxis.offsetText.get_position()[0] == 1: |
|
self.yaxis.offsetText.set_visible(False) |
|
|
|
|
|
def _draw_rasterized(figure, artists, renderer): |
|
""" |
|
A helper function for rasterizing the list of artists. |
|
|
|
The bookkeeping to track if we are or are not in rasterizing mode |
|
with the mixed-mode backends is relatively complicated and is now |
|
handled in the matplotlib.artist.allow_rasterization decorator. |
|
|
|
This helper defines the absolute minimum methods and attributes on a |
|
shim class to be compatible with that decorator and then uses it to |
|
rasterize the list of artists. |
|
|
|
This is maybe too-clever, but allows us to re-use the same code that is |
|
used on normal artists to participate in the "are we rasterizing" |
|
accounting. |
|
|
|
Please do not use this outside of the "rasterize below a given zorder" |
|
functionality of Axes. |
|
|
|
Parameters |
|
---------- |
|
figure : matplotlib.figure.Figure |
|
The figure all of the artists belong to (not checked). We need this |
|
because we can at the figure level suppress composition and insert each |
|
rasterized artist as its own image. |
|
|
|
artists : List[matplotlib.artist.Artist] |
|
The list of Artists to be rasterized. These are assumed to all |
|
be in the same Figure. |
|
|
|
renderer : matplotlib.backendbases.RendererBase |
|
The currently active renderer |
|
|
|
Returns |
|
------- |
|
None |
|
|
|
""" |
|
class _MinimalArtist: |
|
def get_rasterized(self): |
|
return True |
|
|
|
def get_agg_filter(self): |
|
return None |
|
|
|
def __init__(self, figure, artists): |
|
self.figure = figure |
|
self.artists = artists |
|
|
|
@martist.allow_rasterization |
|
def draw(self, renderer): |
|
for a in self.artists: |
|
a.draw(renderer) |
|
|
|
return _MinimalArtist(figure, artists).draw(renderer) |
|
|