aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1
diff options
context:
space:
mode:
authorshumkovnd <shumkovnd@yandex-team.com>2023-11-10 14:39:34 +0300
committershumkovnd <shumkovnd@yandex-team.com>2023-11-10 16:42:24 +0300
commit77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch)
treec51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
downloadydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1')
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/__init__.py10
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/anchored_artists.py462
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_divider.py694
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_grid.py550
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_rgb.py157
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_size.py248
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/inset_locator.py561
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/mpl_axes.py128
-rw-r--r--contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/parasite_axes.py257
9 files changed, 3067 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/__init__.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/__init__.py
new file mode 100644
index 0000000000..c55302485e
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/__init__.py
@@ -0,0 +1,10 @@
+from . import axes_size as Size
+from .axes_divider import Divider, SubplotDivider, make_axes_locatable
+from .axes_grid import AxesGrid, Grid, ImageGrid
+
+from .parasite_axes import host_subplot, host_axes
+
+__all__ = ["Size",
+ "Divider", "SubplotDivider", "make_axes_locatable",
+ "AxesGrid", "Grid", "ImageGrid",
+ "host_subplot", "host_axes"]
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/anchored_artists.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/anchored_artists.py
new file mode 100644
index 0000000000..1238310b46
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/anchored_artists.py
@@ -0,0 +1,462 @@
+from matplotlib import _api, transforms
+from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox,
+ DrawingArea, TextArea, VPacker)
+from matplotlib.patches import (Rectangle, Ellipse, ArrowStyle,
+ FancyArrowPatch, PathPatch)
+from matplotlib.text import TextPath
+
+__all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox',
+ 'AnchoredEllipse', 'AnchoredSizeBar', 'AnchoredDirectionArrows']
+
+
+class AnchoredDrawingArea(AnchoredOffsetbox):
+ def __init__(self, width, height, xdescent, ydescent,
+ loc, pad=0.4, borderpad=0.5, prop=None, frameon=True,
+ **kwargs):
+ """
+ An anchored container with a fixed size and fillable `.DrawingArea`.
+
+ Artists added to the *drawing_area* will have their coordinates
+ interpreted as pixels. Any transformations set on the artists will be
+ overridden.
+
+ Parameters
+ ----------
+ width, height : float
+ Width and height of the container, in pixels.
+ xdescent, ydescent : float
+ Descent of the container in the x- and y- direction, in pixels.
+ loc : str
+ Location of this artist. Valid locations are
+ 'upper left', 'upper center', 'upper right',
+ 'center left', 'center', 'center right',
+ 'lower left', 'lower center', 'lower right'.
+ For backward compatibility, numeric values are accepted as well.
+ See the parameter *loc* of `.Legend` for details.
+ pad : float, default: 0.4
+ Padding around the child objects, in fraction of the font size.
+ borderpad : float, default: 0.5
+ Border padding, in fraction of the font size.
+ prop : `~matplotlib.font_manager.FontProperties`, optional
+ Font property used as a reference for paddings.
+ frameon : bool, default: True
+ If True, draw a box around this artist.
+ **kwargs
+ Keyword arguments forwarded to `.AnchoredOffsetbox`.
+
+ Attributes
+ ----------
+ drawing_area : `~matplotlib.offsetbox.DrawingArea`
+ A container for artists to display.
+
+ Examples
+ --------
+ To display blue and red circles of different sizes in the upper right
+ of an Axes *ax*:
+
+ >>> ada = AnchoredDrawingArea(20, 20, 0, 0,
+ ... loc='upper right', frameon=False)
+ >>> ada.drawing_area.add_artist(Circle((10, 10), 10, fc="b"))
+ >>> ada.drawing_area.add_artist(Circle((30, 10), 5, fc="r"))
+ >>> ax.add_artist(ada)
+ """
+ self.da = DrawingArea(width, height, xdescent, ydescent)
+ self.drawing_area = self.da
+
+ super().__init__(
+ loc, pad=pad, borderpad=borderpad, child=self.da, prop=None,
+ frameon=frameon, **kwargs
+ )
+
+
+class AnchoredAuxTransformBox(AnchoredOffsetbox):
+ def __init__(self, transform, loc,
+ pad=0.4, borderpad=0.5, prop=None, frameon=True, **kwargs):
+ """
+ An anchored container with transformed coordinates.
+
+ Artists added to the *drawing_area* are scaled according to the
+ coordinates of the transformation used. The dimensions of this artist
+ will scale to contain the artists added.
+
+ Parameters
+ ----------
+ transform : `~matplotlib.transforms.Transform`
+ The transformation object for the coordinate system in use, i.e.,
+ :attr:`matplotlib.axes.Axes.transData`.
+ loc : str
+ Location of this artist. Valid locations are
+ 'upper left', 'upper center', 'upper right',
+ 'center left', 'center', 'center right',
+ 'lower left', 'lower center', 'lower right'.
+ For backward compatibility, numeric values are accepted as well.
+ See the parameter *loc* of `.Legend` for details.
+ pad : float, default: 0.4
+ Padding around the child objects, in fraction of the font size.
+ borderpad : float, default: 0.5
+ Border padding, in fraction of the font size.
+ prop : `~matplotlib.font_manager.FontProperties`, optional
+ Font property used as a reference for paddings.
+ frameon : bool, default: True
+ If True, draw a box around this artist.
+ **kwargs
+ Keyword arguments forwarded to `.AnchoredOffsetbox`.
+
+ Attributes
+ ----------
+ drawing_area : `~matplotlib.offsetbox.AuxTransformBox`
+ A container for artists to display.
+
+ Examples
+ --------
+ To display an ellipse in the upper left, with a width of 0.1 and
+ height of 0.4 in data coordinates:
+
+ >>> box = AnchoredAuxTransformBox(ax.transData, loc='upper left')
+ >>> el = Ellipse((0, 0), width=0.1, height=0.4, angle=30)
+ >>> box.drawing_area.add_artist(el)
+ >>> ax.add_artist(box)
+ """
+ self.drawing_area = AuxTransformBox(transform)
+
+ super().__init__(loc, pad=pad, borderpad=borderpad,
+ child=self.drawing_area, prop=prop, frameon=frameon,
+ **kwargs)
+
+
+@_api.deprecated("3.8")
+class AnchoredEllipse(AnchoredOffsetbox):
+ def __init__(self, transform, width, height, angle, loc,
+ pad=0.1, borderpad=0.1, prop=None, frameon=True, **kwargs):
+ """
+ Draw an anchored ellipse of a given size.
+
+ Parameters
+ ----------
+ transform : `~matplotlib.transforms.Transform`
+ The transformation object for the coordinate system in use, i.e.,
+ :attr:`matplotlib.axes.Axes.transData`.
+ width, height : float
+ Width and height of the ellipse, given in coordinates of
+ *transform*.
+ angle : float
+ Rotation of the ellipse, in degrees, anti-clockwise.
+ loc : str
+ Location of the ellipse. Valid locations are
+ 'upper left', 'upper center', 'upper right',
+ 'center left', 'center', 'center right',
+ 'lower left', 'lower center', 'lower right'.
+ For backward compatibility, numeric values are accepted as well.
+ See the parameter *loc* of `.Legend` for details.
+ pad : float, default: 0.1
+ Padding around the ellipse, in fraction of the font size.
+ borderpad : float, default: 0.1
+ Border padding, in fraction of the font size.
+ frameon : bool, default: True
+ If True, draw a box around the ellipse.
+ prop : `~matplotlib.font_manager.FontProperties`, optional
+ Font property used as a reference for paddings.
+ **kwargs
+ Keyword arguments forwarded to `.AnchoredOffsetbox`.
+
+ Attributes
+ ----------
+ ellipse : `~matplotlib.patches.Ellipse`
+ Ellipse patch drawn.
+ """
+ self._box = AuxTransformBox(transform)
+ self.ellipse = Ellipse((0, 0), width, height, angle=angle)
+ self._box.add_artist(self.ellipse)
+
+ super().__init__(loc, pad=pad, borderpad=borderpad, child=self._box,
+ prop=prop, frameon=frameon, **kwargs)
+
+
+class AnchoredSizeBar(AnchoredOffsetbox):
+ def __init__(self, transform, size, label, loc,
+ pad=0.1, borderpad=0.1, sep=2,
+ frameon=True, size_vertical=0, color='black',
+ label_top=False, fontproperties=None, fill_bar=None,
+ **kwargs):
+ """
+ Draw a horizontal scale bar with a center-aligned label underneath.
+
+ Parameters
+ ----------
+ transform : `~matplotlib.transforms.Transform`
+ The transformation object for the coordinate system in use, i.e.,
+ :attr:`matplotlib.axes.Axes.transData`.
+ size : float
+ Horizontal length of the size bar, given in coordinates of
+ *transform*.
+ label : str
+ Label to display.
+ loc : str
+ Location of the size bar. Valid locations are
+ 'upper left', 'upper center', 'upper right',
+ 'center left', 'center', 'center right',
+ 'lower left', 'lower center', 'lower right'.
+ For backward compatibility, numeric values are accepted as well.
+ See the parameter *loc* of `.Legend` for details.
+ pad : float, default: 0.1
+ Padding around the label and size bar, in fraction of the font
+ size.
+ borderpad : float, default: 0.1
+ Border padding, in fraction of the font size.
+ sep : float, default: 2
+ Separation between the label and the size bar, in points.
+ frameon : bool, default: True
+ If True, draw a box around the horizontal bar and label.
+ size_vertical : float, default: 0
+ Vertical length of the size bar, given in coordinates of
+ *transform*.
+ color : str, default: 'black'
+ Color for the size bar and label.
+ label_top : bool, default: False
+ If True, the label will be over the size bar.
+ fontproperties : `~matplotlib.font_manager.FontProperties`, optional
+ Font properties for the label text.
+ fill_bar : bool, optional
+ If True and if *size_vertical* is nonzero, the size bar will
+ be filled in with the color specified by the size bar.
+ Defaults to True if *size_vertical* is greater than
+ zero and False otherwise.
+ **kwargs
+ Keyword arguments forwarded to `.AnchoredOffsetbox`.
+
+ Attributes
+ ----------
+ size_bar : `~matplotlib.offsetbox.AuxTransformBox`
+ Container for the size bar.
+ txt_label : `~matplotlib.offsetbox.TextArea`
+ Container for the label of the size bar.
+
+ Notes
+ -----
+ If *prop* is passed as a keyword argument, but *fontproperties* is
+ not, then *prop* is assumed to be the intended *fontproperties*.
+ Using both *prop* and *fontproperties* is not supported.
+
+ Examples
+ --------
+ >>> import matplotlib.pyplot as plt
+ >>> import numpy as np
+ >>> from mpl_toolkits.axes_grid1.anchored_artists import (
+ ... AnchoredSizeBar)
+ >>> fig, ax = plt.subplots()
+ >>> ax.imshow(np.random.random((10, 10)))
+ >>> bar = AnchoredSizeBar(ax.transData, 3, '3 data units', 4)
+ >>> ax.add_artist(bar)
+ >>> fig.show()
+
+ Using all the optional parameters
+
+ >>> import matplotlib.font_manager as fm
+ >>> fontprops = fm.FontProperties(size=14, family='monospace')
+ >>> bar = AnchoredSizeBar(ax.transData, 3, '3 units', 4, pad=0.5,
+ ... sep=5, borderpad=0.5, frameon=False,
+ ... size_vertical=0.5, color='white',
+ ... fontproperties=fontprops)
+ """
+ if fill_bar is None:
+ fill_bar = size_vertical > 0
+
+ self.size_bar = AuxTransformBox(transform)
+ self.size_bar.add_artist(Rectangle((0, 0), size, size_vertical,
+ fill=fill_bar, facecolor=color,
+ edgecolor=color))
+
+ if fontproperties is None and 'prop' in kwargs:
+ fontproperties = kwargs.pop('prop')
+
+ if fontproperties is None:
+ textprops = {'color': color}
+ else:
+ textprops = {'color': color, 'fontproperties': fontproperties}
+
+ self.txt_label = TextArea(label, textprops=textprops)
+
+ if label_top:
+ _box_children = [self.txt_label, self.size_bar]
+ else:
+ _box_children = [self.size_bar, self.txt_label]
+
+ self._box = VPacker(children=_box_children,
+ align="center",
+ pad=0, sep=sep)
+
+ super().__init__(loc, pad=pad, borderpad=borderpad, child=self._box,
+ prop=fontproperties, frameon=frameon, **kwargs)
+
+
+class AnchoredDirectionArrows(AnchoredOffsetbox):
+ def __init__(self, transform, label_x, label_y, length=0.15,
+ fontsize=0.08, loc='upper left', angle=0, aspect_ratio=1,
+ pad=0.4, borderpad=0.4, frameon=False, color='w', alpha=1,
+ sep_x=0.01, sep_y=0, fontproperties=None, back_length=0.15,
+ head_width=10, head_length=15, tail_width=2,
+ text_props=None, arrow_props=None,
+ **kwargs):
+ """
+ Draw two perpendicular arrows to indicate directions.
+
+ Parameters
+ ----------
+ transform : `~matplotlib.transforms.Transform`
+ The transformation object for the coordinate system in use, i.e.,
+ :attr:`matplotlib.axes.Axes.transAxes`.
+ label_x, label_y : str
+ Label text for the x and y arrows
+ length : float, default: 0.15
+ Length of the arrow, given in coordinates of *transform*.
+ fontsize : float, default: 0.08
+ Size of label strings, given in coordinates of *transform*.
+ loc : str, default: 'upper left'
+ Location of the arrow. Valid locations are
+ 'upper left', 'upper center', 'upper right',
+ 'center left', 'center', 'center right',
+ 'lower left', 'lower center', 'lower right'.
+ For backward compatibility, numeric values are accepted as well.
+ See the parameter *loc* of `.Legend` for details.
+ angle : float, default: 0
+ The angle of the arrows in degrees.
+ aspect_ratio : float, default: 1
+ The ratio of the length of arrow_x and arrow_y.
+ Negative numbers can be used to change the direction.
+ pad : float, default: 0.4
+ Padding around the labels and arrows, in fraction of the font size.
+ borderpad : float, default: 0.4
+ Border padding, in fraction of the font size.
+ frameon : bool, default: False
+ If True, draw a box around the arrows and labels.
+ color : str, default: 'white'
+ Color for the arrows and labels.
+ alpha : float, default: 1
+ Alpha values of the arrows and labels
+ sep_x, sep_y : float, default: 0.01 and 0 respectively
+ Separation between the arrows and labels in coordinates of
+ *transform*.
+ fontproperties : `~matplotlib.font_manager.FontProperties`, optional
+ Font properties for the label text.
+ back_length : float, default: 0.15
+ Fraction of the arrow behind the arrow crossing.
+ head_width : float, default: 10
+ Width of arrow head, sent to `.ArrowStyle`.
+ head_length : float, default: 15
+ Length of arrow head, sent to `.ArrowStyle`.
+ tail_width : float, default: 2
+ Width of arrow tail, sent to `.ArrowStyle`.
+ text_props, arrow_props : dict
+ Properties of the text and arrows, passed to `.TextPath` and
+ `.FancyArrowPatch`.
+ **kwargs
+ Keyword arguments forwarded to `.AnchoredOffsetbox`.
+
+ Attributes
+ ----------
+ arrow_x, arrow_y : `~matplotlib.patches.FancyArrowPatch`
+ Arrow x and y
+ text_path_x, text_path_y : `~matplotlib.text.TextPath`
+ Path for arrow labels
+ p_x, p_y : `~matplotlib.patches.PathPatch`
+ Patch for arrow labels
+ box : `~matplotlib.offsetbox.AuxTransformBox`
+ Container for the arrows and labels.
+
+ Notes
+ -----
+ If *prop* is passed as a keyword argument, but *fontproperties* is
+ not, then *prop* is assumed to be the intended *fontproperties*.
+ Using both *prop* and *fontproperties* is not supported.
+
+ Examples
+ --------
+ >>> import matplotlib.pyplot as plt
+ >>> import numpy as np
+ >>> from mpl_toolkits.axes_grid1.anchored_artists import (
+ ... AnchoredDirectionArrows)
+ >>> fig, ax = plt.subplots()
+ >>> ax.imshow(np.random.random((10, 10)))
+ >>> arrows = AnchoredDirectionArrows(ax.transAxes, '111', '110')
+ >>> ax.add_artist(arrows)
+ >>> fig.show()
+
+ Using several of the optional parameters, creating downward pointing
+ arrow and high contrast text labels.
+
+ >>> import matplotlib.font_manager as fm
+ >>> fontprops = fm.FontProperties(family='monospace')
+ >>> arrows = AnchoredDirectionArrows(ax.transAxes, 'East', 'South',
+ ... loc='lower left', color='k',
+ ... aspect_ratio=-1, sep_x=0.02,
+ ... sep_y=-0.01,
+ ... text_props={'ec':'w', 'fc':'k'},
+ ... fontproperties=fontprops)
+ """
+ if arrow_props is None:
+ arrow_props = {}
+
+ if text_props is None:
+ text_props = {}
+
+ arrowstyle = ArrowStyle("Simple",
+ head_width=head_width,
+ head_length=head_length,
+ tail_width=tail_width)
+
+ if fontproperties is None and 'prop' in kwargs:
+ fontproperties = kwargs.pop('prop')
+
+ if 'color' not in arrow_props:
+ arrow_props['color'] = color
+
+ if 'alpha' not in arrow_props:
+ arrow_props['alpha'] = alpha
+
+ if 'color' not in text_props:
+ text_props['color'] = color
+
+ if 'alpha' not in text_props:
+ text_props['alpha'] = alpha
+
+ t_start = transform
+ t_end = t_start + transforms.Affine2D().rotate_deg(angle)
+
+ self.box = AuxTransformBox(t_end)
+
+ length_x = length
+ length_y = length*aspect_ratio
+
+ self.arrow_x = FancyArrowPatch(
+ (0, back_length*length_y),
+ (length_x, back_length*length_y),
+ arrowstyle=arrowstyle,
+ shrinkA=0.0,
+ shrinkB=0.0,
+ **arrow_props)
+
+ self.arrow_y = FancyArrowPatch(
+ (back_length*length_x, 0),
+ (back_length*length_x, length_y),
+ arrowstyle=arrowstyle,
+ shrinkA=0.0,
+ shrinkB=0.0,
+ **arrow_props)
+
+ self.box.add_artist(self.arrow_x)
+ self.box.add_artist(self.arrow_y)
+
+ text_path_x = TextPath((
+ length_x+sep_x, back_length*length_y+sep_y), label_x,
+ size=fontsize, prop=fontproperties)
+ self.p_x = PathPatch(text_path_x, transform=t_start, **text_props)
+ self.box.add_artist(self.p_x)
+
+ text_path_y = TextPath((
+ length_x*back_length+sep_x, length_y*(1-back_length)+sep_y),
+ label_y, size=fontsize, prop=fontproperties)
+ self.p_y = PathPatch(text_path_y, **text_props)
+ self.box.add_artist(self.p_y)
+
+ super().__init__(loc, pad=pad, borderpad=borderpad, child=self.box,
+ frameon=frameon, **kwargs)
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_divider.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_divider.py
new file mode 100644
index 0000000000..f6c38f35db
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_divider.py
@@ -0,0 +1,694 @@
+"""
+Helper classes to adjust the positions of multiple axes at drawing time.
+"""
+
+import functools
+
+import numpy as np
+
+import matplotlib as mpl
+from matplotlib import _api
+from matplotlib.gridspec import SubplotSpec
+import matplotlib.transforms as mtransforms
+from . import axes_size as Size
+
+
+class Divider:
+ """
+ An Axes positioning class.
+
+ The divider is initialized with lists of horizontal and vertical sizes
+ (:mod:`mpl_toolkits.axes_grid1.axes_size`) based on which a given
+ rectangular area will be divided.
+
+ The `new_locator` method then creates a callable object
+ that can be used as the *axes_locator* of the axes.
+ """
+
+ def __init__(self, fig, pos, horizontal, vertical,
+ aspect=None, anchor="C"):
+ """
+ Parameters
+ ----------
+ fig : Figure
+ pos : tuple of 4 floats
+ Position of the rectangle that will be divided.
+ horizontal : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`
+ Sizes for horizontal division.
+ vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`
+ Sizes for vertical division.
+ aspect : bool, optional
+ Whether overall rectangular area is reduced so that the relative
+ part of the horizontal and vertical scales have the same scale.
+ anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \
+'NW', 'W'}, default: 'C'
+ Placement of the reduced rectangle, when *aspect* is True.
+ """
+
+ self._fig = fig
+ self._pos = pos
+ self._horizontal = horizontal
+ self._vertical = vertical
+ self._anchor = anchor
+ self.set_anchor(anchor)
+ self._aspect = aspect
+ self._xrefindex = 0
+ self._yrefindex = 0
+ self._locator = None
+
+ def get_horizontal_sizes(self, renderer):
+ return np.array([s.get_size(renderer) for s in self.get_horizontal()])
+
+ def get_vertical_sizes(self, renderer):
+ return np.array([s.get_size(renderer) for s in self.get_vertical()])
+
+ def set_position(self, pos):
+ """
+ Set the position of the rectangle.
+
+ Parameters
+ ----------
+ pos : tuple of 4 floats
+ position of the rectangle that will be divided
+ """
+ self._pos = pos
+
+ def get_position(self):
+ """Return the position of the rectangle."""
+ return self._pos
+
+ def set_anchor(self, anchor):
+ """
+ Parameters
+ ----------
+ anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \
+'NW', 'W'}
+ 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.).
+
+ See Also
+ --------
+ .Axes.set_anchor
+ """
+ if isinstance(anchor, str):
+ _api.check_in_list(mtransforms.Bbox.coefs, anchor=anchor)
+ elif not isinstance(anchor, (tuple, list)) or len(anchor) != 2:
+ raise TypeError("anchor must be str or 2-tuple")
+ self._anchor = anchor
+
+ def get_anchor(self):
+ """Return the anchor."""
+ return self._anchor
+
+ def get_subplotspec(self):
+ return None
+
+ def set_horizontal(self, h):
+ """
+ Parameters
+ ----------
+ h : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`
+ sizes for horizontal division
+ """
+ self._horizontal = h
+
+ def get_horizontal(self):
+ """Return horizontal sizes."""
+ return self._horizontal
+
+ def set_vertical(self, v):
+ """
+ Parameters
+ ----------
+ v : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`
+ sizes for vertical division
+ """
+ self._vertical = v
+
+ def get_vertical(self):
+ """Return vertical sizes."""
+ return self._vertical
+
+ def set_aspect(self, aspect=False):
+ """
+ Parameters
+ ----------
+ aspect : bool
+ """
+ self._aspect = aspect
+
+ def get_aspect(self):
+ """Return aspect."""
+ return self._aspect
+
+ def set_locator(self, _locator):
+ self._locator = _locator
+
+ def get_locator(self):
+ return self._locator
+
+ def get_position_runtime(self, ax, renderer):
+ if self._locator is None:
+ return self.get_position()
+ else:
+ return self._locator(ax, renderer).bounds
+
+ @staticmethod
+ def _calc_k(sizes, total):
+ # sizes is a (n, 2) array of (rel_size, abs_size); this method finds
+ # the k factor such that sum(rel_size * k + abs_size) == total.
+ rel_sum, abs_sum = sizes.sum(0)
+ return (total - abs_sum) / rel_sum if rel_sum else 0
+
+ @staticmethod
+ def _calc_offsets(sizes, k):
+ # Apply k factors to (n, 2) sizes array of (rel_size, abs_size); return
+ # the resulting cumulative offset positions.
+ return np.cumsum([0, *(sizes @ [k, 1])])
+
+ def new_locator(self, nx, ny, nx1=None, ny1=None):
+ """
+ Return an axes locator callable for the specified cell.
+
+ Parameters
+ ----------
+ nx, nx1 : int
+ Integers specifying the column-position of the
+ cell. When *nx1* is None, a single *nx*-th column is
+ specified. Otherwise, location of columns spanning between *nx*
+ to *nx1* (but excluding *nx1*-th column) is specified.
+ ny, ny1 : int
+ Same as *nx* and *nx1*, but for row positions.
+ """
+ if nx1 is None:
+ nx1 = nx + 1
+ if ny1 is None:
+ ny1 = ny + 1
+ # append_size("left") adds a new size at the beginning of the
+ # horizontal size lists; this shift transforms e.g.
+ # new_locator(nx=2, ...) into effectively new_locator(nx=3, ...). To
+ # take that into account, instead of recording nx, we record
+ # nx-self._xrefindex, where _xrefindex is shifted by 1 by each
+ # append_size("left"), and re-add self._xrefindex back to nx in
+ # _locate, when the actual axes position is computed. Ditto for y.
+ xref = self._xrefindex
+ yref = self._yrefindex
+ locator = functools.partial(
+ self._locate, nx - xref, ny - yref, nx1 - xref, ny1 - yref)
+ locator.get_subplotspec = self.get_subplotspec
+ return locator
+
+ @_api.deprecated(
+ "3.8", alternative="divider.new_locator(...)(ax, renderer)")
+ def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
+ """
+ Implementation of ``divider.new_locator().__call__``.
+
+ Parameters
+ ----------
+ nx, nx1 : int
+ Integers specifying the column-position of the cell. When *nx1* is
+ None, a single *nx*-th column is specified. Otherwise, the
+ location of columns spanning between *nx* to *nx1* (but excluding
+ *nx1*-th column) is specified.
+ ny, ny1 : int
+ Same as *nx* and *nx1*, but for row positions.
+ axes
+ renderer
+ """
+ xref = self._xrefindex
+ yref = self._yrefindex
+ return self._locate(
+ nx - xref, (nx + 1 if nx1 is None else nx1) - xref,
+ ny - yref, (ny + 1 if ny1 is None else ny1) - yref,
+ axes, renderer)
+
+ def _locate(self, nx, ny, nx1, ny1, axes, renderer):
+ """
+ Implementation of ``divider.new_locator().__call__``.
+
+ The axes locator callable returned by ``new_locator()`` is created as
+ a `functools.partial` of this method with *nx*, *ny*, *nx1*, and *ny1*
+ specifying the requested cell.
+ """
+ nx += self._xrefindex
+ nx1 += self._xrefindex
+ ny += self._yrefindex
+ ny1 += self._yrefindex
+
+ fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
+ x, y, w, h = self.get_position_runtime(axes, renderer)
+
+ hsizes = self.get_horizontal_sizes(renderer)
+ vsizes = self.get_vertical_sizes(renderer)
+ k_h = self._calc_k(hsizes, fig_w * w)
+ k_v = self._calc_k(vsizes, fig_h * h)
+
+ if self.get_aspect():
+ k = min(k_h, k_v)
+ ox = self._calc_offsets(hsizes, k)
+ oy = self._calc_offsets(vsizes, k)
+
+ ww = (ox[-1] - ox[0]) / fig_w
+ hh = (oy[-1] - oy[0]) / fig_h
+ pb = mtransforms.Bbox.from_bounds(x, y, w, h)
+ pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
+ x0, y0 = pb1.anchored(self.get_anchor(), pb).p0
+
+ else:
+ ox = self._calc_offsets(hsizes, k_h)
+ oy = self._calc_offsets(vsizes, k_v)
+ x0, y0 = x, y
+
+ if nx1 is None:
+ nx1 = -1
+ if ny1 is None:
+ ny1 = -1
+
+ x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w
+ y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h
+
+ return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
+
+ def append_size(self, position, size):
+ _api.check_in_list(["left", "right", "bottom", "top"],
+ position=position)
+ if position == "left":
+ self._horizontal.insert(0, size)
+ self._xrefindex += 1
+ elif position == "right":
+ self._horizontal.append(size)
+ elif position == "bottom":
+ self._vertical.insert(0, size)
+ self._yrefindex += 1
+ else: # 'top'
+ self._vertical.append(size)
+
+ def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None):
+ """
+ Add auto-adjustable padding around *use_axes* to take their decorations
+ (title, labels, ticks, ticklabels) into account during layout.
+
+ Parameters
+ ----------
+ use_axes : `~matplotlib.axes.Axes` or list of `~matplotlib.axes.Axes`
+ The Axes whose decorations are taken into account.
+ pad : float, default: 0.1
+ Additional padding in inches.
+ adjust_dirs : list of {"left", "right", "bottom", "top"}, optional
+ The sides where padding is added; defaults to all four sides.
+ """
+ if adjust_dirs is None:
+ adjust_dirs = ["left", "right", "bottom", "top"]
+ for d in adjust_dirs:
+ self.append_size(d, Size._AxesDecorationsSize(use_axes, d) + pad)
+
+
+@_api.deprecated("3.8")
+class AxesLocator:
+ """
+ A callable object which returns the position and size of a given
+ `.AxesDivider` cell.
+ """
+
+ def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None):
+ """
+ Parameters
+ ----------
+ axes_divider : `~mpl_toolkits.axes_grid1.axes_divider.AxesDivider`
+ nx, nx1 : int
+ Integers specifying the column-position of the
+ cell. When *nx1* is None, a single *nx*-th column is
+ specified. Otherwise, location of columns spanning between *nx*
+ to *nx1* (but excluding *nx1*-th column) is specified.
+ ny, ny1 : int
+ Same as *nx* and *nx1*, but for row positions.
+ """
+ self._axes_divider = axes_divider
+
+ _xrefindex = axes_divider._xrefindex
+ _yrefindex = axes_divider._yrefindex
+
+ self._nx, self._ny = nx - _xrefindex, ny - _yrefindex
+
+ if nx1 is None:
+ nx1 = len(self._axes_divider)
+ if ny1 is None:
+ ny1 = len(self._axes_divider[0])
+
+ self._nx1 = nx1 - _xrefindex
+ self._ny1 = ny1 - _yrefindex
+
+ def __call__(self, axes, renderer):
+
+ _xrefindex = self._axes_divider._xrefindex
+ _yrefindex = self._axes_divider._yrefindex
+
+ return self._axes_divider.locate(self._nx + _xrefindex,
+ self._ny + _yrefindex,
+ self._nx1 + _xrefindex,
+ self._ny1 + _yrefindex,
+ axes,
+ renderer)
+
+ def get_subplotspec(self):
+ return self._axes_divider.get_subplotspec()
+
+
+class SubplotDivider(Divider):
+ """
+ The Divider class whose rectangle area is specified as a subplot geometry.
+ """
+
+ def __init__(self, fig, *args, horizontal=None, vertical=None,
+ aspect=None, anchor='C'):
+ """
+ Parameters
+ ----------
+ fig : `~matplotlib.figure.Figure`
+
+ *args : tuple (*nrows*, *ncols*, *index*) or int
+ The array of subplots in the figure has dimensions ``(nrows,
+ ncols)``, and *index* is the index of the subplot being created.
+ *index* starts at 1 in the upper left corner and increases to the
+ right.
+
+ If *nrows*, *ncols*, and *index* are all single digit numbers, then
+ *args* can be passed as a single 3-digit number (e.g. 234 for
+ (2, 3, 4)).
+ horizontal : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`, optional
+ Sizes for horizontal division.
+ vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`, optional
+ Sizes for vertical division.
+ aspect : bool, optional
+ Whether overall rectangular area is reduced so that the relative
+ part of the horizontal and vertical scales have the same scale.
+ anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', \
+'NW', 'W'}, default: 'C'
+ Placement of the reduced rectangle, when *aspect* is True.
+ """
+ self.figure = fig
+ super().__init__(fig, [0, 0, 1, 1],
+ horizontal=horizontal or [], vertical=vertical or [],
+ aspect=aspect, anchor=anchor)
+ self.set_subplotspec(SubplotSpec._from_subplot_args(fig, args))
+
+ def get_position(self):
+ """Return the bounds of the subplot box."""
+ return self.get_subplotspec().get_position(self.figure).bounds
+
+ def get_subplotspec(self):
+ """Get the SubplotSpec instance."""
+ return self._subplotspec
+
+ def set_subplotspec(self, subplotspec):
+ """Set the SubplotSpec instance."""
+ self._subplotspec = subplotspec
+ self.set_position(subplotspec.get_position(self.figure))
+
+
+class AxesDivider(Divider):
+ """
+ Divider based on the preexisting axes.
+ """
+
+ def __init__(self, axes, xref=None, yref=None):
+ """
+ Parameters
+ ----------
+ axes : :class:`~matplotlib.axes.Axes`
+ xref
+ yref
+ """
+ self._axes = axes
+ if xref is None:
+ self._xref = Size.AxesX(axes)
+ else:
+ self._xref = xref
+ if yref is None:
+ self._yref = Size.AxesY(axes)
+ else:
+ self._yref = yref
+
+ super().__init__(fig=axes.get_figure(), pos=None,
+ horizontal=[self._xref], vertical=[self._yref],
+ aspect=None, anchor="C")
+
+ def _get_new_axes(self, *, axes_class=None, **kwargs):
+ axes = self._axes
+ if axes_class is None:
+ axes_class = type(axes)
+ return axes_class(axes.get_figure(), axes.get_position(original=True),
+ **kwargs)
+
+ def new_horizontal(self, size, pad=None, pack_start=False, **kwargs):
+ """
+ Helper method for ``append_axes("left")`` and ``append_axes("right")``.
+
+ See the documentation of `append_axes` for more details.
+
+ :meta private:
+ """
+ if pad is None:
+ pad = mpl.rcParams["figure.subplot.wspace"] * self._xref
+ pos = "left" if pack_start else "right"
+ if pad:
+ if not isinstance(pad, Size._Base):
+ pad = Size.from_any(pad, fraction_ref=self._xref)
+ self.append_size(pos, pad)
+ if not isinstance(size, Size._Base):
+ size = Size.from_any(size, fraction_ref=self._xref)
+ self.append_size(pos, size)
+ locator = self.new_locator(
+ nx=0 if pack_start else len(self._horizontal) - 1,
+ ny=self._yrefindex)
+ ax = self._get_new_axes(**kwargs)
+ ax.set_axes_locator(locator)
+ return ax
+
+ def new_vertical(self, size, pad=None, pack_start=False, **kwargs):
+ """
+ Helper method for ``append_axes("top")`` and ``append_axes("bottom")``.
+
+ See the documentation of `append_axes` for more details.
+
+ :meta private:
+ """
+ if pad is None:
+ pad = mpl.rcParams["figure.subplot.hspace"] * self._yref
+ pos = "bottom" if pack_start else "top"
+ if pad:
+ if not isinstance(pad, Size._Base):
+ pad = Size.from_any(pad, fraction_ref=self._yref)
+ self.append_size(pos, pad)
+ if not isinstance(size, Size._Base):
+ size = Size.from_any(size, fraction_ref=self._yref)
+ self.append_size(pos, size)
+ locator = self.new_locator(
+ nx=self._xrefindex,
+ ny=0 if pack_start else len(self._vertical) - 1)
+ ax = self._get_new_axes(**kwargs)
+ ax.set_axes_locator(locator)
+ return ax
+
+ def append_axes(self, position, size, pad=None, *, axes_class=None,
+ **kwargs):
+ """
+ Add a new axes on a given side of the main axes.
+
+ Parameters
+ ----------
+ position : {"left", "right", "bottom", "top"}
+ Where the new axes is positioned relative to the main axes.
+ size : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
+ The axes width or height. float or str arguments are interpreted
+ as ``axes_size.from_any(size, AxesX(<main_axes>))`` for left or
+ right axes, and likewise with ``AxesY`` for bottom or top axes.
+ pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
+ Padding between the axes. float or str arguments are interpreted
+ as for *size*. Defaults to :rc:`figure.subplot.wspace` times the
+ main Axes width (left or right axes) or :rc:`figure.subplot.hspace`
+ times the main Axes height (bottom or top axes).
+ axes_class : subclass type of `~.axes.Axes`, optional
+ The type of the new axes. Defaults to the type of the main axes.
+ **kwargs
+ All extra keywords arguments are passed to the created axes.
+ """
+ create_axes, pack_start = _api.check_getitem({
+ "left": (self.new_horizontal, True),
+ "right": (self.new_horizontal, False),
+ "bottom": (self.new_vertical, True),
+ "top": (self.new_vertical, False),
+ }, position=position)
+ ax = create_axes(
+ size, pad, pack_start=pack_start, axes_class=axes_class, **kwargs)
+ self._fig.add_axes(ax)
+ return ax
+
+ def get_aspect(self):
+ if self._aspect is None:
+ aspect = self._axes.get_aspect()
+ if aspect == "auto":
+ return False
+ else:
+ return True
+ else:
+ return self._aspect
+
+ def get_position(self):
+ if self._pos is None:
+ bbox = self._axes.get_position(original=True)
+ return bbox.bounds
+ else:
+ return self._pos
+
+ def get_anchor(self):
+ if self._anchor is None:
+ return self._axes.get_anchor()
+ else:
+ return self._anchor
+
+ def get_subplotspec(self):
+ return self._axes.get_subplotspec()
+
+
+# Helper for HBoxDivider/VBoxDivider.
+# The variable names are written for a horizontal layout, but the calculations
+# work identically for vertical layouts.
+def _locate(x, y, w, h, summed_widths, equal_heights, fig_w, fig_h, anchor):
+
+ total_width = fig_w * w
+ max_height = fig_h * h
+
+ # Determine the k factors.
+ n = len(equal_heights)
+ eq_rels, eq_abss = equal_heights.T
+ sm_rels, sm_abss = summed_widths.T
+ A = np.diag([*eq_rels, 0])
+ A[:n, -1] = -1
+ A[-1, :-1] = sm_rels
+ B = [*(-eq_abss), total_width - sm_abss.sum()]
+ # A @ K = B: This finds factors {k_0, ..., k_{N-1}, H} so that
+ # eq_rel_i * k_i + eq_abs_i = H for all i: all axes have the same height
+ # sum(sm_rel_i * k_i + sm_abs_i) = total_width: fixed total width
+ # (foo_rel_i * k_i + foo_abs_i will end up being the size of foo.)
+ *karray, height = np.linalg.solve(A, B)
+ if height > max_height: # Additionally, upper-bound the height.
+ karray = (max_height - eq_abss) / eq_rels
+
+ # Compute the offsets corresponding to these factors.
+ ox = np.cumsum([0, *(sm_rels * karray + sm_abss)])
+ ww = (ox[-1] - ox[0]) / fig_w
+ h0_rel, h0_abs = equal_heights[0]
+ hh = (karray[0]*h0_rel + h0_abs) / fig_h
+ pb = mtransforms.Bbox.from_bounds(x, y, w, h)
+ pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
+ x0, y0 = pb1.anchored(anchor, pb).p0
+
+ return x0, y0, ox, hh
+
+
+class HBoxDivider(SubplotDivider):
+ """
+ A `.SubplotDivider` for laying out axes horizontally, while ensuring that
+ they have equal heights.
+
+ Examples
+ --------
+ .. plot:: gallery/axes_grid1/demo_axes_hbox_divider.py
+ """
+
+ def new_locator(self, nx, nx1=None):
+ """
+ Create an axes locator callable for the specified cell.
+
+ Parameters
+ ----------
+ nx, nx1 : int
+ Integers specifying the column-position of the
+ cell. When *nx1* is None, a single *nx*-th column is
+ specified. Otherwise, location of columns spanning between *nx*
+ to *nx1* (but excluding *nx1*-th column) is specified.
+ """
+ return super().new_locator(nx, 0, nx1, 0)
+
+ def _locate(self, nx, ny, nx1, ny1, axes, renderer):
+ # docstring inherited
+ nx += self._xrefindex
+ nx1 += self._xrefindex
+ fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
+ x, y, w, h = self.get_position_runtime(axes, renderer)
+ summed_ws = self.get_horizontal_sizes(renderer)
+ equal_hs = self.get_vertical_sizes(renderer)
+ x0, y0, ox, hh = _locate(
+ x, y, w, h, summed_ws, equal_hs, fig_w, fig_h, self.get_anchor())
+ if nx1 is None:
+ nx1 = -1
+ x1, w1 = x0 + ox[nx] / fig_w, (ox[nx1] - ox[nx]) / fig_w
+ y1, h1 = y0, hh
+ return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
+
+
+class VBoxDivider(SubplotDivider):
+ """
+ A `.SubplotDivider` for laying out axes vertically, while ensuring that
+ they have equal widths.
+ """
+
+ def new_locator(self, ny, ny1=None):
+ """
+ Create an axes locator callable for the specified cell.
+
+ Parameters
+ ----------
+ ny, ny1 : int
+ Integers specifying the row-position of the
+ cell. When *ny1* is None, a single *ny*-th row is
+ specified. Otherwise, location of rows spanning between *ny*
+ to *ny1* (but excluding *ny1*-th row) is specified.
+ """
+ return super().new_locator(0, ny, 0, ny1)
+
+ def _locate(self, nx, ny, nx1, ny1, axes, renderer):
+ # docstring inherited
+ ny += self._yrefindex
+ ny1 += self._yrefindex
+ fig_w, fig_h = self._fig.bbox.size / self._fig.dpi
+ x, y, w, h = self.get_position_runtime(axes, renderer)
+ summed_hs = self.get_vertical_sizes(renderer)
+ equal_ws = self.get_horizontal_sizes(renderer)
+ y0, x0, oy, ww = _locate(
+ y, x, h, w, summed_hs, equal_ws, fig_h, fig_w, self.get_anchor())
+ if ny1 is None:
+ ny1 = -1
+ x1, w1 = x0, ww
+ y1, h1 = y0 + oy[ny] / fig_h, (oy[ny1] - oy[ny]) / fig_h
+ return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
+
+
+def make_axes_locatable(axes):
+ divider = AxesDivider(axes)
+ locator = divider.new_locator(nx=0, ny=0)
+ axes.set_axes_locator(locator)
+
+ return divider
+
+
+def make_axes_area_auto_adjustable(
+ ax, use_axes=None, pad=0.1, adjust_dirs=None):
+ """
+ Add auto-adjustable padding around *ax* to take its decorations (title,
+ labels, ticks, ticklabels) into account during layout, using
+ `.Divider.add_auto_adjustable_area`.
+
+ By default, padding is determined from the decorations of *ax*.
+ Pass *use_axes* to consider the decorations of other Axes instead.
+ """
+ if adjust_dirs is None:
+ adjust_dirs = ["left", "right", "bottom", "top"]
+ divider = make_axes_locatable(ax)
+ if use_axes is None:
+ use_axes = ax
+ divider.add_auto_adjustable_area(use_axes=use_axes, pad=pad,
+ adjust_dirs=adjust_dirs)
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_grid.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_grid.py
new file mode 100644
index 0000000000..720d985414
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_grid.py
@@ -0,0 +1,550 @@
+from numbers import Number
+import functools
+from types import MethodType
+
+import numpy as np
+
+from matplotlib import _api, cbook
+from matplotlib.gridspec import SubplotSpec
+
+from .axes_divider import Size, SubplotDivider, Divider
+from .mpl_axes import Axes, SimpleAxisArtist
+
+
+class CbarAxesBase:
+ def __init__(self, *args, orientation, **kwargs):
+ self.orientation = orientation
+ super().__init__(*args, **kwargs)
+
+ def colorbar(self, mappable, **kwargs):
+ return self.figure.colorbar(
+ mappable, cax=self, location=self.orientation, **kwargs)
+
+ @_api.deprecated("3.8", alternative="ax.tick_params and colorbar.set_label")
+ def toggle_label(self, b):
+ axis = self.axis[self.orientation]
+ axis.toggle(ticklabels=b, label=b)
+
+
+_cbaraxes_class_factory = cbook._make_class_factory(CbarAxesBase, "Cbar{}")
+
+
+class Grid:
+ """
+ A grid of Axes.
+
+ In Matplotlib, the Axes location (and size) is specified in normalized
+ figure coordinates. This may not be ideal for images that needs to be
+ displayed with a given aspect ratio; for example, it is difficult to
+ display multiple images of a same size with some fixed padding between
+ them. AxesGrid can be used in such case.
+ """
+
+ _defaultAxesClass = Axes
+
+ def __init__(self, fig,
+ rect,
+ nrows_ncols,
+ ngrids=None,
+ direction="row",
+ axes_pad=0.02,
+ *,
+ share_all=False,
+ share_x=True,
+ share_y=True,
+ label_mode="L",
+ axes_class=None,
+ aspect=False,
+ ):
+ """
+ Parameters
+ ----------
+ fig : `.Figure`
+ The parent figure.
+ rect : (float, float, float, float), (int, int, int), int, or \
+ `~.SubplotSpec`
+ The axes position, as a ``(left, bottom, width, height)`` tuple,
+ as a three-digit subplot position code (e.g., ``(1, 2, 1)`` or
+ ``121``), or as a `~.SubplotSpec`.
+ nrows_ncols : (int, int)
+ Number of rows and columns in the grid.
+ ngrids : int or None, default: None
+ If not None, only the first *ngrids* axes in the grid are created.
+ direction : {"row", "column"}, default: "row"
+ Whether axes are created in row-major ("row by row") or
+ column-major order ("column by column"). This also affects the
+ order in which axes are accessed using indexing (``grid[index]``).
+ axes_pad : float or (float, float), default: 0.02
+ Padding or (horizontal padding, vertical padding) between axes, in
+ inches.
+ share_all : bool, default: False
+ Whether all axes share their x- and y-axis. Overrides *share_x*
+ and *share_y*.
+ share_x : bool, default: True
+ Whether all axes of a column share their x-axis.
+ share_y : bool, default: True
+ Whether all axes of a row share their y-axis.
+ label_mode : {"L", "1", "all", "keep"}, default: "L"
+ Determines which axes will get tick labels:
+
+ - "L": All axes on the left column get vertical tick labels;
+ all axes on the bottom row get horizontal tick labels.
+ - "1": Only the bottom left axes is labelled.
+ - "all": All axes are labelled.
+ - "keep": Do not do anything.
+
+ axes_class : subclass of `matplotlib.axes.Axes`, default: None
+ aspect : bool, default: False
+ Whether the axes aspect ratio follows the aspect ratio of the data
+ limits.
+ """
+ self._nrows, self._ncols = nrows_ncols
+
+ if ngrids is None:
+ ngrids = self._nrows * self._ncols
+ else:
+ if not 0 < ngrids <= self._nrows * self._ncols:
+ raise ValueError(
+ "ngrids must be positive and not larger than nrows*ncols")
+
+ self.ngrids = ngrids
+
+ self._horiz_pad_size, self._vert_pad_size = map(
+ Size.Fixed, np.broadcast_to(axes_pad, 2))
+
+ _api.check_in_list(["column", "row"], direction=direction)
+ self._direction = direction
+
+ if axes_class is None:
+ axes_class = self._defaultAxesClass
+ elif isinstance(axes_class, (list, tuple)):
+ cls, kwargs = axes_class
+ axes_class = functools.partial(cls, **kwargs)
+
+ kw = dict(horizontal=[], vertical=[], aspect=aspect)
+ if isinstance(rect, (Number, SubplotSpec)):
+ self._divider = SubplotDivider(fig, rect, **kw)
+ elif len(rect) == 3:
+ self._divider = SubplotDivider(fig, *rect, **kw)
+ elif len(rect) == 4:
+ self._divider = Divider(fig, rect, **kw)
+ else:
+ raise TypeError("Incorrect rect format")
+
+ rect = self._divider.get_position()
+
+ axes_array = np.full((self._nrows, self._ncols), None, dtype=object)
+ for i in range(self.ngrids):
+ col, row = self._get_col_row(i)
+ if share_all:
+ sharex = sharey = axes_array[0, 0]
+ else:
+ sharex = axes_array[0, col] if share_x else None
+ sharey = axes_array[row, 0] if share_y else None
+ axes_array[row, col] = axes_class(
+ fig, rect, sharex=sharex, sharey=sharey)
+ self.axes_all = axes_array.ravel(
+ order="C" if self._direction == "row" else "F").tolist()
+ self.axes_column = axes_array.T.tolist()
+ self.axes_row = axes_array.tolist()
+ self.axes_llc = self.axes_column[0][-1]
+
+ self._init_locators()
+
+ for ax in self.axes_all:
+ fig.add_axes(ax)
+
+ self.set_label_mode(label_mode)
+
+ def _init_locators(self):
+ self._divider.set_horizontal(
+ [Size.Scaled(1), self._horiz_pad_size] * (self._ncols-1) + [Size.Scaled(1)])
+ self._divider.set_vertical(
+ [Size.Scaled(1), self._vert_pad_size] * (self._nrows-1) + [Size.Scaled(1)])
+ for i in range(self.ngrids):
+ col, row = self._get_col_row(i)
+ self.axes_all[i].set_axes_locator(
+ self._divider.new_locator(nx=2 * col, ny=2 * (self._nrows - 1 - row)))
+
+ def _get_col_row(self, n):
+ if self._direction == "column":
+ col, row = divmod(n, self._nrows)
+ else:
+ row, col = divmod(n, self._ncols)
+
+ return col, row
+
+ # Good to propagate __len__ if we have __getitem__
+ def __len__(self):
+ return len(self.axes_all)
+
+ def __getitem__(self, i):
+ return self.axes_all[i]
+
+ def get_geometry(self):
+ """
+ Return the number of rows and columns of the grid as (nrows, ncols).
+ """
+ return self._nrows, self._ncols
+
+ def set_axes_pad(self, axes_pad):
+ """
+ Set the padding between the axes.
+
+ Parameters
+ ----------
+ axes_pad : (float, float)
+ The padding (horizontal pad, vertical pad) in inches.
+ """
+ self._horiz_pad_size.fixed_size = axes_pad[0]
+ self._vert_pad_size.fixed_size = axes_pad[1]
+
+ def get_axes_pad(self):
+ """
+ Return the axes padding.
+
+ Returns
+ -------
+ hpad, vpad
+ Padding (horizontal pad, vertical pad) in inches.
+ """
+ return (self._horiz_pad_size.fixed_size,
+ self._vert_pad_size.fixed_size)
+
+ def set_aspect(self, aspect):
+ """Set the aspect of the SubplotDivider."""
+ self._divider.set_aspect(aspect)
+
+ def get_aspect(self):
+ """Return the aspect of the SubplotDivider."""
+ return self._divider.get_aspect()
+
+ def set_label_mode(self, mode):
+ """
+ Define which axes have tick labels.
+
+ Parameters
+ ----------
+ mode : {"L", "1", "all", "keep"}
+ The label mode:
+
+ - "L": All axes on the left column get vertical tick labels;
+ all axes on the bottom row get horizontal tick labels.
+ - "1": Only the bottom left axes is labelled.
+ - "all": All axes are labelled.
+ - "keep": Do not do anything.
+ """
+ is_last_row, is_first_col = (
+ np.mgrid[:self._nrows, :self._ncols] == [[[self._nrows - 1]], [[0]]])
+ if mode == "all":
+ bottom = left = np.full((self._nrows, self._ncols), True)
+ elif mode == "L":
+ bottom = is_last_row
+ left = is_first_col
+ elif mode == "1":
+ bottom = left = is_last_row & is_first_col
+ else:
+ # Use _api.check_in_list at the top of the method when deprecation
+ # period expires
+ if mode != 'keep':
+ _api.warn_deprecated(
+ '3.7', name="Grid label_mode",
+ message='Passing an undefined label_mode is deprecated '
+ 'since %(since)s and will become an error '
+ '%(removal)s. To silence this warning, pass '
+ '"keep", which gives the same behaviour.')
+ return
+ for i in range(self._nrows):
+ for j in range(self._ncols):
+ ax = self.axes_row[i][j]
+ if isinstance(ax.axis, MethodType):
+ bottom_axis = SimpleAxisArtist(ax.xaxis, 1, ax.spines["bottom"])
+ left_axis = SimpleAxisArtist(ax.yaxis, 1, ax.spines["left"])
+ else:
+ bottom_axis = ax.axis["bottom"]
+ left_axis = ax.axis["left"]
+ bottom_axis.toggle(ticklabels=bottom[i, j], label=bottom[i, j])
+ left_axis.toggle(ticklabels=left[i, j], label=left[i, j])
+
+ def get_divider(self):
+ return self._divider
+
+ def set_axes_locator(self, locator):
+ self._divider.set_locator(locator)
+
+ def get_axes_locator(self):
+ return self._divider.get_locator()
+
+
+class ImageGrid(Grid):
+ """
+ A grid of Axes for Image display.
+
+ This class is a specialization of `~.axes_grid1.axes_grid.Grid` for displaying a
+ grid of images. In particular, it forces all axes in a column to share their x-axis
+ and all axes in a row to share their y-axis. It further provides helpers to add
+ colorbars to some or all axes.
+ """
+
+ def __init__(self, fig,
+ rect,
+ nrows_ncols,
+ ngrids=None,
+ direction="row",
+ axes_pad=0.02,
+ *,
+ share_all=False,
+ aspect=True,
+ label_mode="L",
+ cbar_mode=None,
+ cbar_location="right",
+ cbar_pad=None,
+ cbar_size="5%",
+ cbar_set_cax=True,
+ axes_class=None,
+ ):
+ """
+ Parameters
+ ----------
+ fig : `.Figure`
+ The parent figure.
+ rect : (float, float, float, float) or int
+ The axes position, as a ``(left, bottom, width, height)`` tuple or
+ as a three-digit subplot position code (e.g., "121").
+ nrows_ncols : (int, int)
+ Number of rows and columns in the grid.
+ ngrids : int or None, default: None
+ If not None, only the first *ngrids* axes in the grid are created.
+ direction : {"row", "column"}, default: "row"
+ Whether axes are created in row-major ("row by row") or
+ column-major order ("column by column"). This also affects the
+ order in which axes are accessed using indexing (``grid[index]``).
+ axes_pad : float or (float, float), default: 0.02in
+ Padding or (horizontal padding, vertical padding) between axes, in
+ inches.
+ share_all : bool, default: False
+ Whether all axes share their x- and y-axis. Note that in any case,
+ all axes in a column share their x-axis and all axes in a row share
+ their y-axis.
+ aspect : bool, default: True
+ Whether the axes aspect ratio follows the aspect ratio of the data
+ limits.
+ label_mode : {"L", "1", "all"}, default: "L"
+ Determines which axes will get tick labels:
+
+ - "L": All axes on the left column get vertical tick labels;
+ all axes on the bottom row get horizontal tick labels.
+ - "1": Only the bottom left axes is labelled.
+ - "all": all axes are labelled.
+
+ cbar_mode : {"each", "single", "edge", None}, default: None
+ Whether to create a colorbar for "each" axes, a "single" colorbar
+ for the entire grid, colorbars only for axes on the "edge"
+ determined by *cbar_location*, or no colorbars. The colorbars are
+ stored in the :attr:`cbar_axes` attribute.
+ cbar_location : {"left", "right", "bottom", "top"}, default: "right"
+ cbar_pad : float, default: None
+ Padding between the image axes and the colorbar axes.
+ cbar_size : size specification (see `.Size.from_any`), default: "5%"
+ Colorbar size.
+ cbar_set_cax : bool, default: True
+ If True, each axes in the grid has a *cax* attribute that is bound
+ to associated *cbar_axes*.
+ axes_class : subclass of `matplotlib.axes.Axes`, default: None
+ """
+ _api.check_in_list(["each", "single", "edge", None],
+ cbar_mode=cbar_mode)
+ _api.check_in_list(["left", "right", "bottom", "top"],
+ cbar_location=cbar_location)
+ self._colorbar_mode = cbar_mode
+ self._colorbar_location = cbar_location
+ self._colorbar_pad = cbar_pad
+ self._colorbar_size = cbar_size
+ # The colorbar axes are created in _init_locators().
+
+ super().__init__(
+ fig, rect, nrows_ncols, ngrids,
+ direction=direction, axes_pad=axes_pad,
+ share_all=share_all, share_x=True, share_y=True, aspect=aspect,
+ label_mode=label_mode, axes_class=axes_class)
+
+ for ax in self.cbar_axes:
+ fig.add_axes(ax)
+
+ if cbar_set_cax:
+ if self._colorbar_mode == "single":
+ for ax in self.axes_all:
+ ax.cax = self.cbar_axes[0]
+ elif self._colorbar_mode == "edge":
+ for index, ax in enumerate(self.axes_all):
+ col, row = self._get_col_row(index)
+ if self._colorbar_location in ("left", "right"):
+ ax.cax = self.cbar_axes[row]
+ else:
+ ax.cax = self.cbar_axes[col]
+ else:
+ for ax, cax in zip(self.axes_all, self.cbar_axes):
+ ax.cax = cax
+
+ def _init_locators(self):
+ # Slightly abusing this method to inject colorbar creation into init.
+
+ if self._colorbar_pad is None:
+ # horizontal or vertical arrangement?
+ if self._colorbar_location in ("left", "right"):
+ self._colorbar_pad = self._horiz_pad_size.fixed_size
+ else:
+ self._colorbar_pad = self._vert_pad_size.fixed_size
+ self.cbar_axes = [
+ _cbaraxes_class_factory(self._defaultAxesClass)(
+ self.axes_all[0].figure, self._divider.get_position(),
+ orientation=self._colorbar_location)
+ for _ in range(self.ngrids)]
+
+ cb_mode = self._colorbar_mode
+ cb_location = self._colorbar_location
+
+ h = []
+ v = []
+
+ h_ax_pos = []
+ h_cb_pos = []
+ if cb_mode == "single" and cb_location in ("left", "bottom"):
+ if cb_location == "left":
+ sz = self._nrows * Size.AxesX(self.axes_llc)
+ h.append(Size.from_any(self._colorbar_size, sz))
+ h.append(Size.from_any(self._colorbar_pad, sz))
+ locator = self._divider.new_locator(nx=0, ny=0, ny1=-1)
+ elif cb_location == "bottom":
+ sz = self._ncols * Size.AxesY(self.axes_llc)
+ v.append(Size.from_any(self._colorbar_size, sz))
+ v.append(Size.from_any(self._colorbar_pad, sz))
+ locator = self._divider.new_locator(nx=0, nx1=-1, ny=0)
+ for i in range(self.ngrids):
+ self.cbar_axes[i].set_visible(False)
+ self.cbar_axes[0].set_axes_locator(locator)
+ self.cbar_axes[0].set_visible(True)
+
+ for col, ax in enumerate(self.axes_row[0]):
+ if h:
+ h.append(self._horiz_pad_size)
+
+ if ax:
+ sz = Size.AxesX(ax, aspect="axes", ref_ax=self.axes_all[0])
+ else:
+ sz = Size.AxesX(self.axes_all[0],
+ aspect="axes", ref_ax=self.axes_all[0])
+
+ if (cb_location == "left"
+ and (cb_mode == "each"
+ or (cb_mode == "edge" and col == 0))):
+ h_cb_pos.append(len(h))
+ h.append(Size.from_any(self._colorbar_size, sz))
+ h.append(Size.from_any(self._colorbar_pad, sz))
+
+ h_ax_pos.append(len(h))
+ h.append(sz)
+
+ if (cb_location == "right"
+ and (cb_mode == "each"
+ or (cb_mode == "edge" and col == self._ncols - 1))):
+ h.append(Size.from_any(self._colorbar_pad, sz))
+ h_cb_pos.append(len(h))
+ h.append(Size.from_any(self._colorbar_size, sz))
+
+ v_ax_pos = []
+ v_cb_pos = []
+ for row, ax in enumerate(self.axes_column[0][::-1]):
+ if v:
+ v.append(self._vert_pad_size)
+
+ if ax:
+ sz = Size.AxesY(ax, aspect="axes", ref_ax=self.axes_all[0])
+ else:
+ sz = Size.AxesY(self.axes_all[0],
+ aspect="axes", ref_ax=self.axes_all[0])
+
+ if (cb_location == "bottom"
+ and (cb_mode == "each"
+ or (cb_mode == "edge" and row == 0))):
+ v_cb_pos.append(len(v))
+ v.append(Size.from_any(self._colorbar_size, sz))
+ v.append(Size.from_any(self._colorbar_pad, sz))
+
+ v_ax_pos.append(len(v))
+ v.append(sz)
+
+ if (cb_location == "top"
+ and (cb_mode == "each"
+ or (cb_mode == "edge" and row == self._nrows - 1))):
+ v.append(Size.from_any(self._colorbar_pad, sz))
+ v_cb_pos.append(len(v))
+ v.append(Size.from_any(self._colorbar_size, sz))
+
+ for i in range(self.ngrids):
+ col, row = self._get_col_row(i)
+ locator = self._divider.new_locator(nx=h_ax_pos[col],
+ ny=v_ax_pos[self._nrows-1-row])
+ self.axes_all[i].set_axes_locator(locator)
+
+ if cb_mode == "each":
+ if cb_location in ("right", "left"):
+ locator = self._divider.new_locator(
+ nx=h_cb_pos[col], ny=v_ax_pos[self._nrows - 1 - row])
+
+ elif cb_location in ("top", "bottom"):
+ locator = self._divider.new_locator(
+ nx=h_ax_pos[col], ny=v_cb_pos[self._nrows - 1 - row])
+
+ self.cbar_axes[i].set_axes_locator(locator)
+ elif cb_mode == "edge":
+ if (cb_location == "left" and col == 0
+ or cb_location == "right" and col == self._ncols - 1):
+ locator = self._divider.new_locator(
+ nx=h_cb_pos[0], ny=v_ax_pos[self._nrows - 1 - row])
+ self.cbar_axes[row].set_axes_locator(locator)
+ elif (cb_location == "bottom" and row == self._nrows - 1
+ or cb_location == "top" and row == 0):
+ locator = self._divider.new_locator(nx=h_ax_pos[col],
+ ny=v_cb_pos[0])
+ self.cbar_axes[col].set_axes_locator(locator)
+
+ if cb_mode == "single":
+ if cb_location == "right":
+ sz = self._nrows * Size.AxesX(self.axes_llc)
+ h.append(Size.from_any(self._colorbar_pad, sz))
+ h.append(Size.from_any(self._colorbar_size, sz))
+ locator = self._divider.new_locator(nx=-2, ny=0, ny1=-1)
+ elif cb_location == "top":
+ sz = self._ncols * Size.AxesY(self.axes_llc)
+ v.append(Size.from_any(self._colorbar_pad, sz))
+ v.append(Size.from_any(self._colorbar_size, sz))
+ locator = self._divider.new_locator(nx=0, nx1=-1, ny=-2)
+ if cb_location in ("right", "top"):
+ for i in range(self.ngrids):
+ self.cbar_axes[i].set_visible(False)
+ self.cbar_axes[0].set_axes_locator(locator)
+ self.cbar_axes[0].set_visible(True)
+ elif cb_mode == "each":
+ for i in range(self.ngrids):
+ self.cbar_axes[i].set_visible(True)
+ elif cb_mode == "edge":
+ if cb_location in ("right", "left"):
+ count = self._nrows
+ else:
+ count = self._ncols
+ for i in range(count):
+ self.cbar_axes[i].set_visible(True)
+ for j in range(i + 1, self.ngrids):
+ self.cbar_axes[j].set_visible(False)
+ else:
+ for i in range(self.ngrids):
+ self.cbar_axes[i].set_visible(False)
+ self.cbar_axes[i].set_position([1., 1., 0.001, 0.001],
+ which="active")
+
+ self._divider.set_horizontal(h)
+ self._divider.set_vertical(v)
+
+
+AxesGrid = ImageGrid
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_rgb.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_rgb.py
new file mode 100644
index 0000000000..52fd707e87
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_rgb.py
@@ -0,0 +1,157 @@
+from types import MethodType
+
+import numpy as np
+
+from .axes_divider import make_axes_locatable, Size
+from .mpl_axes import Axes, SimpleAxisArtist
+
+
+def make_rgb_axes(ax, pad=0.01, axes_class=None, **kwargs):
+ """
+ Parameters
+ ----------
+ ax : `~matplotlib.axes.Axes`
+ Axes instance to create the RGB Axes in.
+ pad : float, optional
+ Fraction of the Axes height to pad.
+ axes_class : `matplotlib.axes.Axes` or None, optional
+ Axes class to use for the R, G, and B Axes. If None, use
+ the same class as *ax*.
+ **kwargs
+ Forwarded to *axes_class* init for the R, G, and B Axes.
+ """
+
+ divider = make_axes_locatable(ax)
+
+ pad_size = pad * Size.AxesY(ax)
+
+ xsize = ((1-2*pad)/3) * Size.AxesX(ax)
+ ysize = ((1-2*pad)/3) * Size.AxesY(ax)
+
+ divider.set_horizontal([Size.AxesX(ax), pad_size, xsize])
+ divider.set_vertical([ysize, pad_size, ysize, pad_size, ysize])
+
+ ax.set_axes_locator(divider.new_locator(0, 0, ny1=-1))
+
+ ax_rgb = []
+ if axes_class is None:
+ axes_class = type(ax)
+
+ for ny in [4, 2, 0]:
+ ax1 = axes_class(ax.get_figure(), ax.get_position(original=True),
+ sharex=ax, sharey=ax, **kwargs)
+ locator = divider.new_locator(nx=2, ny=ny)
+ ax1.set_axes_locator(locator)
+ for t in ax1.yaxis.get_ticklabels() + ax1.xaxis.get_ticklabels():
+ t.set_visible(False)
+ try:
+ for axis in ax1.axis.values():
+ axis.major_ticklabels.set_visible(False)
+ except AttributeError:
+ pass
+
+ ax_rgb.append(ax1)
+
+ fig = ax.get_figure()
+ for ax1 in ax_rgb:
+ fig.add_axes(ax1)
+
+ return ax_rgb
+
+
+class RGBAxes:
+ """
+ 4-panel `~.Axes.imshow` (RGB, R, G, B).
+
+ Layout::
+
+ ┌───────────────┬─────┐
+ │ │ R │
+ │ ├─────┤
+ │ RGB │ G │
+ │ ├─────┤
+ │ │ B │
+ └───────────────┴─────┘
+
+ Subclasses can override the ``_defaultAxesClass`` attribute.
+ By default RGBAxes uses `.mpl_axes.Axes`.
+
+ Attributes
+ ----------
+ RGB : ``_defaultAxesClass``
+ The Axes object for the three-channel `~.Axes.imshow`.
+ R : ``_defaultAxesClass``
+ The Axes object for the red channel `~.Axes.imshow`.
+ G : ``_defaultAxesClass``
+ The Axes object for the green channel `~.Axes.imshow`.
+ B : ``_defaultAxesClass``
+ The Axes object for the blue channel `~.Axes.imshow`.
+ """
+
+ _defaultAxesClass = Axes
+
+ def __init__(self, *args, pad=0, **kwargs):
+ """
+ Parameters
+ ----------
+ pad : float, default: 0
+ Fraction of the Axes height to put as padding.
+ axes_class : `~matplotlib.axes.Axes`
+ Axes class to use. If not provided, ``_defaultAxesClass`` is used.
+ *args
+ Forwarded to *axes_class* init for the RGB Axes
+ **kwargs
+ Forwarded to *axes_class* init for the RGB, R, G, and B Axes
+ """
+ axes_class = kwargs.pop("axes_class", self._defaultAxesClass)
+ self.RGB = ax = axes_class(*args, **kwargs)
+ ax.get_figure().add_axes(ax)
+ self.R, self.G, self.B = make_rgb_axes(
+ ax, pad=pad, axes_class=axes_class, **kwargs)
+ # Set the line color and ticks for the axes.
+ for ax1 in [self.RGB, self.R, self.G, self.B]:
+ if isinstance(ax1.axis, MethodType):
+ ad = Axes.AxisDict(self)
+ ad.update(
+ bottom=SimpleAxisArtist(ax1.xaxis, 1, ax1.spines["bottom"]),
+ top=SimpleAxisArtist(ax1.xaxis, 2, ax1.spines["top"]),
+ left=SimpleAxisArtist(ax1.yaxis, 1, ax1.spines["left"]),
+ right=SimpleAxisArtist(ax1.yaxis, 2, ax1.spines["right"]))
+ else:
+ ad = ax1.axis
+ ad[:].line.set_color("w")
+ ad[:].major_ticks.set_markeredgecolor("w")
+
+ def imshow_rgb(self, r, g, b, **kwargs):
+ """
+ Create the four images {rgb, r, g, b}.
+
+ Parameters
+ ----------
+ r, g, b : array-like
+ The red, green, and blue arrays.
+ **kwargs
+ Forwarded to `~.Axes.imshow` calls for the four images.
+
+ Returns
+ -------
+ rgb : `~matplotlib.image.AxesImage`
+ r : `~matplotlib.image.AxesImage`
+ g : `~matplotlib.image.AxesImage`
+ b : `~matplotlib.image.AxesImage`
+ """
+ if not (r.shape == g.shape == b.shape):
+ raise ValueError(
+ f'Input shapes ({r.shape}, {g.shape}, {b.shape}) do not match')
+ RGB = np.dstack([r, g, b])
+ R = np.zeros_like(RGB)
+ R[:, :, 0] = r
+ G = np.zeros_like(RGB)
+ G[:, :, 1] = g
+ B = np.zeros_like(RGB)
+ B[:, :, 2] = b
+ im_rgb = self.RGB.imshow(RGB, **kwargs)
+ im_r = self.R.imshow(R, **kwargs)
+ im_g = self.G.imshow(G, **kwargs)
+ im_b = self.B.imshow(B, **kwargs)
+ return im_rgb, im_r, im_g, im_b
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_size.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_size.py
new file mode 100644
index 0000000000..d251472077
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/axes_size.py
@@ -0,0 +1,248 @@
+"""
+Provides classes of simple units that will be used with `.AxesDivider`
+class (or others) to determine the size of each Axes. The unit
+classes define `get_size` method that returns a tuple of two floats,
+meaning relative and absolute sizes, respectively.
+
+Note that this class is nothing more than a simple tuple of two
+floats. Take a look at the Divider class to see how these two
+values are used.
+"""
+
+from numbers import Real
+
+from matplotlib import _api
+from matplotlib.axes import Axes
+
+
+class _Base:
+ def __rmul__(self, other):
+ return Fraction(other, self)
+
+ def __add__(self, other):
+ if isinstance(other, _Base):
+ return Add(self, other)
+ else:
+ return Add(self, Fixed(other))
+
+ def get_size(self, renderer):
+ """
+ Return two-float tuple with relative and absolute sizes.
+ """
+ raise NotImplementedError("Subclasses must implement")
+
+
+class Add(_Base):
+ """
+ Sum of two sizes.
+ """
+
+ def __init__(self, a, b):
+ self._a = a
+ self._b = b
+
+ def get_size(self, renderer):
+ a_rel_size, a_abs_size = self._a.get_size(renderer)
+ b_rel_size, b_abs_size = self._b.get_size(renderer)
+ return a_rel_size + b_rel_size, a_abs_size + b_abs_size
+
+
+class Fixed(_Base):
+ """
+ Simple fixed size with absolute part = *fixed_size* and relative part = 0.
+ """
+
+ def __init__(self, fixed_size):
+ _api.check_isinstance(Real, fixed_size=fixed_size)
+ self.fixed_size = fixed_size
+
+ def get_size(self, renderer):
+ rel_size = 0.
+ abs_size = self.fixed_size
+ return rel_size, abs_size
+
+
+class Scaled(_Base):
+ """
+ Simple scaled(?) size with absolute part = 0 and
+ relative part = *scalable_size*.
+ """
+
+ def __init__(self, scalable_size):
+ self._scalable_size = scalable_size
+
+ def get_size(self, renderer):
+ rel_size = self._scalable_size
+ abs_size = 0.
+ return rel_size, abs_size
+
+Scalable = Scaled
+
+
+def _get_axes_aspect(ax):
+ aspect = ax.get_aspect()
+ if aspect == "auto":
+ aspect = 1.
+ return aspect
+
+
+class AxesX(_Base):
+ """
+ Scaled size whose relative part corresponds to the data width
+ of the *axes* multiplied by the *aspect*.
+ """
+
+ def __init__(self, axes, aspect=1., ref_ax=None):
+ self._axes = axes
+ self._aspect = aspect
+ if aspect == "axes" and ref_ax is None:
+ raise ValueError("ref_ax must be set when aspect='axes'")
+ self._ref_ax = ref_ax
+
+ def get_size(self, renderer):
+ l1, l2 = self._axes.get_xlim()
+ if self._aspect == "axes":
+ ref_aspect = _get_axes_aspect(self._ref_ax)
+ aspect = ref_aspect / _get_axes_aspect(self._axes)
+ else:
+ aspect = self._aspect
+
+ rel_size = abs(l2-l1)*aspect
+ abs_size = 0.
+ return rel_size, abs_size
+
+
+class AxesY(_Base):
+ """
+ Scaled size whose relative part corresponds to the data height
+ of the *axes* multiplied by the *aspect*.
+ """
+
+ def __init__(self, axes, aspect=1., ref_ax=None):
+ self._axes = axes
+ self._aspect = aspect
+ if aspect == "axes" and ref_ax is None:
+ raise ValueError("ref_ax must be set when aspect='axes'")
+ self._ref_ax = ref_ax
+
+ def get_size(self, renderer):
+ l1, l2 = self._axes.get_ylim()
+
+ if self._aspect == "axes":
+ ref_aspect = _get_axes_aspect(self._ref_ax)
+ aspect = _get_axes_aspect(self._axes)
+ else:
+ aspect = self._aspect
+
+ rel_size = abs(l2-l1)*aspect
+ abs_size = 0.
+ return rel_size, abs_size
+
+
+class MaxExtent(_Base):
+ """
+ Size whose absolute part is either the largest width or the largest height
+ of the given *artist_list*.
+ """
+
+ def __init__(self, artist_list, w_or_h):
+ self._artist_list = artist_list
+ _api.check_in_list(["width", "height"], w_or_h=w_or_h)
+ self._w_or_h = w_or_h
+
+ def add_artist(self, a):
+ self._artist_list.append(a)
+
+ def get_size(self, renderer):
+ rel_size = 0.
+ extent_list = [
+ getattr(a.get_window_extent(renderer), self._w_or_h) / a.figure.dpi
+ for a in self._artist_list]
+ abs_size = max(extent_list, default=0)
+ return rel_size, abs_size
+
+
+class MaxWidth(MaxExtent):
+ """
+ Size whose absolute part is the largest width of the given *artist_list*.
+ """
+
+ def __init__(self, artist_list):
+ super().__init__(artist_list, "width")
+
+
+class MaxHeight(MaxExtent):
+ """
+ Size whose absolute part is the largest height of the given *artist_list*.
+ """
+
+ def __init__(self, artist_list):
+ super().__init__(artist_list, "height")
+
+
+class Fraction(_Base):
+ """
+ An instance whose size is a *fraction* of the *ref_size*.
+
+ >>> s = Fraction(0.3, AxesX(ax))
+ """
+
+ def __init__(self, fraction, ref_size):
+ _api.check_isinstance(Real, fraction=fraction)
+ self._fraction_ref = ref_size
+ self._fraction = fraction
+
+ def get_size(self, renderer):
+ if self._fraction_ref is None:
+ return self._fraction, 0.
+ else:
+ r, a = self._fraction_ref.get_size(renderer)
+ rel_size = r*self._fraction
+ abs_size = a*self._fraction
+ return rel_size, abs_size
+
+
+def from_any(size, fraction_ref=None):
+ """
+ Create a Fixed unit when the first argument is a float, or a
+ Fraction unit if that is a string that ends with %. The second
+ argument is only meaningful when Fraction unit is created.
+
+ >>> from mpl_toolkits.axes_grid1.axes_size import from_any
+ >>> a = from_any(1.2) # => Fixed(1.2)
+ >>> from_any("50%", a) # => Fraction(0.5, a)
+ """
+ if isinstance(size, Real):
+ return Fixed(size)
+ elif isinstance(size, str):
+ if size[-1] == "%":
+ return Fraction(float(size[:-1]) / 100, fraction_ref)
+ raise ValueError("Unknown format")
+
+
+class _AxesDecorationsSize(_Base):
+ """
+ Fixed size, corresponding to the size of decorations on a given Axes side.
+ """
+
+ _get_size_map = {
+ "left": lambda tight_bb, axes_bb: axes_bb.xmin - tight_bb.xmin,
+ "right": lambda tight_bb, axes_bb: tight_bb.xmax - axes_bb.xmax,
+ "bottom": lambda tight_bb, axes_bb: axes_bb.ymin - tight_bb.ymin,
+ "top": lambda tight_bb, axes_bb: tight_bb.ymax - axes_bb.ymax,
+ }
+
+ def __init__(self, ax, direction):
+ self._get_size = _api.check_getitem(
+ self._get_size_map, direction=direction)
+ self._ax_list = [ax] if isinstance(ax, Axes) else ax
+
+ def get_size(self, renderer):
+ sz = max([
+ self._get_size(ax.get_tightbbox(renderer, call_axes_locator=False),
+ ax.bbox)
+ for ax in self._ax_list])
+ dpi = renderer.points_to_pixels(72)
+ abs_size = sz / dpi
+ rel_size = 0
+ return rel_size, abs_size
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/inset_locator.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/inset_locator.py
new file mode 100644
index 0000000000..6d591a4531
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/inset_locator.py
@@ -0,0 +1,561 @@
+"""
+A collection of functions and objects for creating or placing inset axes.
+"""
+
+from matplotlib import _api, _docstring
+from matplotlib.offsetbox import AnchoredOffsetbox
+from matplotlib.patches import Patch, Rectangle
+from matplotlib.path import Path
+from matplotlib.transforms import Bbox, BboxTransformTo
+from matplotlib.transforms import IdentityTransform, TransformedBbox
+
+from . import axes_size as Size
+from .parasite_axes import HostAxes
+
+
+@_api.deprecated("3.8", alternative="Axes.inset_axes")
+class InsetPosition:
+ @_docstring.dedent_interpd
+ def __init__(self, parent, lbwh):
+ """
+ An object for positioning an inset axes.
+
+ This is created by specifying the normalized coordinates in the axes,
+ instead of the figure.
+
+ Parameters
+ ----------
+ parent : `~matplotlib.axes.Axes`
+ Axes to use for normalizing coordinates.
+
+ lbwh : iterable of four floats
+ The left edge, bottom edge, width, and height of the inset axes, in
+ units of the normalized coordinate of the *parent* axes.
+
+ See Also
+ --------
+ :meth:`matplotlib.axes.Axes.set_axes_locator`
+
+ Examples
+ --------
+ The following bounds the inset axes to a box with 20%% of the parent
+ axes height and 40%% of the width. The size of the axes specified
+ ([0, 0, 1, 1]) ensures that the axes completely fills the bounding box:
+
+ >>> parent_axes = plt.gca()
+ >>> ax_ins = plt.axes([0, 0, 1, 1])
+ >>> ip = InsetPosition(parent_axes, [0.5, 0.1, 0.4, 0.2])
+ >>> ax_ins.set_axes_locator(ip)
+ """
+ self.parent = parent
+ self.lbwh = lbwh
+
+ def __call__(self, ax, renderer):
+ bbox_parent = self.parent.get_position(original=False)
+ trans = BboxTransformTo(bbox_parent)
+ bbox_inset = Bbox.from_bounds(*self.lbwh)
+ bb = TransformedBbox(bbox_inset, trans)
+ return bb
+
+
+class AnchoredLocatorBase(AnchoredOffsetbox):
+ def __init__(self, bbox_to_anchor, offsetbox, loc,
+ borderpad=0.5, bbox_transform=None):
+ super().__init__(
+ loc, pad=0., child=None, borderpad=borderpad,
+ bbox_to_anchor=bbox_to_anchor, bbox_transform=bbox_transform
+ )
+
+ def draw(self, renderer):
+ raise RuntimeError("No draw method should be called")
+
+ def __call__(self, ax, renderer):
+ if renderer is None:
+ renderer = ax.figure._get_renderer()
+ self.axes = ax
+ bbox = self.get_window_extent(renderer)
+ px, py = self.get_offset(bbox.width, bbox.height, 0, 0, renderer)
+ bbox_canvas = Bbox.from_bounds(px, py, bbox.width, bbox.height)
+ tr = ax.figure.transSubfigure.inverted()
+ return TransformedBbox(bbox_canvas, tr)
+
+
+class AnchoredSizeLocator(AnchoredLocatorBase):
+ def __init__(self, bbox_to_anchor, x_size, y_size, loc,
+ borderpad=0.5, bbox_transform=None):
+ super().__init__(
+ bbox_to_anchor, None, loc,
+ borderpad=borderpad, bbox_transform=bbox_transform
+ )
+
+ self.x_size = Size.from_any(x_size)
+ self.y_size = Size.from_any(y_size)
+
+ def get_bbox(self, renderer):
+ bbox = self.get_bbox_to_anchor()
+ dpi = renderer.points_to_pixels(72.)
+
+ r, a = self.x_size.get_size(renderer)
+ width = bbox.width * r + a * dpi
+ r, a = self.y_size.get_size(renderer)
+ height = bbox.height * r + a * dpi
+
+ fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
+ pad = self.pad * fontsize
+
+ return Bbox.from_bounds(0, 0, width, height).padded(pad)
+
+
+class AnchoredZoomLocator(AnchoredLocatorBase):
+ def __init__(self, parent_axes, zoom, loc,
+ borderpad=0.5,
+ bbox_to_anchor=None,
+ bbox_transform=None):
+ self.parent_axes = parent_axes
+ self.zoom = zoom
+ if bbox_to_anchor is None:
+ bbox_to_anchor = parent_axes.bbox
+ super().__init__(
+ bbox_to_anchor, None, loc, borderpad=borderpad,
+ bbox_transform=bbox_transform)
+
+ def get_bbox(self, renderer):
+ bb = self.parent_axes.transData.transform_bbox(self.axes.viewLim)
+ fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
+ pad = self.pad * fontsize
+ return (
+ Bbox.from_bounds(
+ 0, 0, abs(bb.width * self.zoom), abs(bb.height * self.zoom))
+ .padded(pad))
+
+
+class BboxPatch(Patch):
+ @_docstring.dedent_interpd
+ def __init__(self, bbox, **kwargs):
+ """
+ Patch showing the shape bounded by a Bbox.
+
+ Parameters
+ ----------
+ bbox : `~matplotlib.transforms.Bbox`
+ Bbox to use for the extents of this patch.
+
+ **kwargs
+ Patch properties. Valid arguments include:
+
+ %(Patch:kwdoc)s
+ """
+ if "transform" in kwargs:
+ raise ValueError("transform should not be set")
+
+ kwargs["transform"] = IdentityTransform()
+ super().__init__(**kwargs)
+ self.bbox = bbox
+
+ def get_path(self):
+ # docstring inherited
+ x0, y0, x1, y1 = self.bbox.extents
+ return Path._create_closed([(x0, y0), (x1, y0), (x1, y1), (x0, y1)])
+
+
+class BboxConnector(Patch):
+ @staticmethod
+ def get_bbox_edge_pos(bbox, loc):
+ """
+ Return the ``(x, y)`` coordinates of corner *loc* of *bbox*; parameters
+ behave as documented for the `.BboxConnector` constructor.
+ """
+ x0, y0, x1, y1 = bbox.extents
+ if loc == 1:
+ return x1, y1
+ elif loc == 2:
+ return x0, y1
+ elif loc == 3:
+ return x0, y0
+ elif loc == 4:
+ return x1, y0
+
+ @staticmethod
+ def connect_bbox(bbox1, bbox2, loc1, loc2=None):
+ """
+ Construct a `.Path` connecting corner *loc1* of *bbox1* to corner
+ *loc2* of *bbox2*, where parameters behave as documented as for the
+ `.BboxConnector` constructor.
+ """
+ if isinstance(bbox1, Rectangle):
+ bbox1 = TransformedBbox(Bbox.unit(), bbox1.get_transform())
+ if isinstance(bbox2, Rectangle):
+ bbox2 = TransformedBbox(Bbox.unit(), bbox2.get_transform())
+ if loc2 is None:
+ loc2 = loc1
+ x1, y1 = BboxConnector.get_bbox_edge_pos(bbox1, loc1)
+ x2, y2 = BboxConnector.get_bbox_edge_pos(bbox2, loc2)
+ return Path([[x1, y1], [x2, y2]])
+
+ @_docstring.dedent_interpd
+ def __init__(self, bbox1, bbox2, loc1, loc2=None, **kwargs):
+ """
+ Connect two bboxes with a straight line.
+
+ Parameters
+ ----------
+ bbox1, bbox2 : `~matplotlib.transforms.Bbox`
+ Bounding boxes to connect.
+
+ loc1, loc2 : {1, 2, 3, 4}
+ Corner of *bbox1* and *bbox2* to draw the line. Valid values are::
+
+ 'upper right' : 1,
+ 'upper left' : 2,
+ 'lower left' : 3,
+ 'lower right' : 4
+
+ *loc2* is optional and defaults to *loc1*.
+
+ **kwargs
+ Patch properties for the line drawn. Valid arguments include:
+
+ %(Patch:kwdoc)s
+ """
+ if "transform" in kwargs:
+ raise ValueError("transform should not be set")
+
+ kwargs["transform"] = IdentityTransform()
+ kwargs.setdefault(
+ "fill", bool({'fc', 'facecolor', 'color'}.intersection(kwargs)))
+ super().__init__(**kwargs)
+ self.bbox1 = bbox1
+ self.bbox2 = bbox2
+ self.loc1 = loc1
+ self.loc2 = loc2
+
+ def get_path(self):
+ # docstring inherited
+ return self.connect_bbox(self.bbox1, self.bbox2,
+ self.loc1, self.loc2)
+
+
+class BboxConnectorPatch(BboxConnector):
+ @_docstring.dedent_interpd
+ def __init__(self, bbox1, bbox2, loc1a, loc2a, loc1b, loc2b, **kwargs):
+ """
+ Connect two bboxes with a quadrilateral.
+
+ The quadrilateral is specified by two lines that start and end at
+ corners of the bboxes. The four sides of the quadrilateral are defined
+ by the two lines given, the line between the two corners specified in
+ *bbox1* and the line between the two corners specified in *bbox2*.
+
+ Parameters
+ ----------
+ bbox1, bbox2 : `~matplotlib.transforms.Bbox`
+ Bounding boxes to connect.
+
+ loc1a, loc2a, loc1b, loc2b : {1, 2, 3, 4}
+ The first line connects corners *loc1a* of *bbox1* and *loc2a* of
+ *bbox2*; the second line connects corners *loc1b* of *bbox1* and
+ *loc2b* of *bbox2*. Valid values are::
+
+ 'upper right' : 1,
+ 'upper left' : 2,
+ 'lower left' : 3,
+ 'lower right' : 4
+
+ **kwargs
+ Patch properties for the line drawn:
+
+ %(Patch:kwdoc)s
+ """
+ if "transform" in kwargs:
+ raise ValueError("transform should not be set")
+ super().__init__(bbox1, bbox2, loc1a, loc2a, **kwargs)
+ self.loc1b = loc1b
+ self.loc2b = loc2b
+
+ def get_path(self):
+ # docstring inherited
+ path1 = self.connect_bbox(self.bbox1, self.bbox2, self.loc1, self.loc2)
+ path2 = self.connect_bbox(self.bbox2, self.bbox1,
+ self.loc2b, self.loc1b)
+ path_merged = [*path1.vertices, *path2.vertices, path1.vertices[0]]
+ return Path(path_merged)
+
+
+def _add_inset_axes(parent_axes, axes_class, axes_kwargs, axes_locator):
+ """Helper function to add an inset axes and disable navigation in it."""
+ if axes_class is None:
+ axes_class = HostAxes
+ if axes_kwargs is None:
+ axes_kwargs = {}
+ inset_axes = axes_class(
+ parent_axes.figure, parent_axes.get_position(),
+ **{"navigate": False, **axes_kwargs, "axes_locator": axes_locator})
+ return parent_axes.figure.add_axes(inset_axes)
+
+
+@_docstring.dedent_interpd
+def inset_axes(parent_axes, width, height, loc='upper right',
+ bbox_to_anchor=None, bbox_transform=None,
+ axes_class=None, axes_kwargs=None,
+ borderpad=0.5):
+ """
+ Create an inset axes with a given width and height.
+
+ Both sizes used can be specified either in inches or percentage.
+ For example,::
+
+ inset_axes(parent_axes, width='40%%', height='30%%', loc='lower left')
+
+ creates in inset axes in the lower left corner of *parent_axes* which spans
+ over 30%% in height and 40%% in width of the *parent_axes*. Since the usage
+ of `.inset_axes` may become slightly tricky when exceeding such standard
+ cases, it is recommended to read :doc:`the examples
+ </gallery/axes_grid1/inset_locator_demo>`.
+
+ Notes
+ -----
+ The meaning of *bbox_to_anchor* and *bbox_to_transform* is interpreted
+ differently from that of legend. The value of bbox_to_anchor
+ (or the return value of its get_points method; the default is
+ *parent_axes.bbox*) is transformed by the bbox_transform (the default
+ is Identity transform) and then interpreted as points in the pixel
+ coordinate (which is dpi dependent).
+
+ Thus, following three calls are identical and creates an inset axes
+ with respect to the *parent_axes*::
+
+ axins = inset_axes(parent_axes, "30%%", "40%%")
+ axins = inset_axes(parent_axes, "30%%", "40%%",
+ bbox_to_anchor=parent_axes.bbox)
+ axins = inset_axes(parent_axes, "30%%", "40%%",
+ bbox_to_anchor=(0, 0, 1, 1),
+ bbox_transform=parent_axes.transAxes)
+
+ Parameters
+ ----------
+ parent_axes : `matplotlib.axes.Axes`
+ Axes to place the inset axes.
+
+ width, height : float or str
+ Size of the inset axes to create. If a float is provided, it is
+ the size in inches, e.g. *width=1.3*. If a string is provided, it is
+ the size in relative units, e.g. *width='40%%'*. By default, i.e. if
+ neither *bbox_to_anchor* nor *bbox_transform* are specified, those
+ are relative to the parent_axes. Otherwise, they are to be understood
+ relative to the bounding box provided via *bbox_to_anchor*.
+
+ loc : str, default: 'upper right'
+ Location to place the inset axes. Valid locations are
+ 'upper left', 'upper center', 'upper right',
+ 'center left', 'center', 'center right',
+ 'lower left', 'lower center', 'lower right'.
+ For backward compatibility, numeric values are accepted as well.
+ See the parameter *loc* of `.Legend` for details.
+
+ bbox_to_anchor : tuple or `~matplotlib.transforms.BboxBase`, optional
+ Bbox that the inset axes will be anchored to. If None,
+ a tuple of (0, 0, 1, 1) is used if *bbox_transform* is set
+ to *parent_axes.transAxes* or *parent_axes.figure.transFigure*.
+ Otherwise, *parent_axes.bbox* is used. If a tuple, can be either
+ [left, bottom, width, height], or [left, bottom].
+ If the kwargs *width* and/or *height* are specified in relative units,
+ the 2-tuple [left, bottom] cannot be used. Note that,
+ unless *bbox_transform* is set, the units of the bounding box
+ are interpreted in the pixel coordinate. When using *bbox_to_anchor*
+ with tuple, it almost always makes sense to also specify
+ a *bbox_transform*. This might often be the axes transform
+ *parent_axes.transAxes*.
+
+ bbox_transform : `~matplotlib.transforms.Transform`, optional
+ Transformation for the bbox that contains the inset axes.
+ If None, a `.transforms.IdentityTransform` is used. The value
+ of *bbox_to_anchor* (or the return value of its get_points method)
+ is transformed by the *bbox_transform* and then interpreted
+ as points in the pixel coordinate (which is dpi dependent).
+ You may provide *bbox_to_anchor* in some normalized coordinate,
+ and give an appropriate transform (e.g., *parent_axes.transAxes*).
+
+ axes_class : `~matplotlib.axes.Axes` type, default: `.HostAxes`
+ The type of the newly created inset axes.
+
+ axes_kwargs : dict, optional
+ Keyword arguments to pass to the constructor of the inset axes.
+ Valid arguments include:
+
+ %(Axes:kwdoc)s
+
+ borderpad : float, default: 0.5
+ Padding between inset axes and the bbox_to_anchor.
+ The units are axes font size, i.e. for a default font size of 10 points
+ *borderpad = 0.5* is equivalent to a padding of 5 points.
+
+ Returns
+ -------
+ inset_axes : *axes_class*
+ Inset axes object created.
+ """
+
+ if (bbox_transform in [parent_axes.transAxes, parent_axes.figure.transFigure]
+ and bbox_to_anchor is None):
+ _api.warn_external("Using the axes or figure transform requires a "
+ "bounding box in the respective coordinates. "
+ "Using bbox_to_anchor=(0, 0, 1, 1) now.")
+ bbox_to_anchor = (0, 0, 1, 1)
+ if bbox_to_anchor is None:
+ bbox_to_anchor = parent_axes.bbox
+ if (isinstance(bbox_to_anchor, tuple) and
+ (isinstance(width, str) or isinstance(height, str))):
+ if len(bbox_to_anchor) != 4:
+ raise ValueError("Using relative units for width or height "
+ "requires to provide a 4-tuple or a "
+ "`Bbox` instance to `bbox_to_anchor.")
+ return _add_inset_axes(
+ parent_axes, axes_class, axes_kwargs,
+ AnchoredSizeLocator(
+ bbox_to_anchor, width, height, loc=loc,
+ bbox_transform=bbox_transform, borderpad=borderpad))
+
+
+@_docstring.dedent_interpd
+def zoomed_inset_axes(parent_axes, zoom, loc='upper right',
+ bbox_to_anchor=None, bbox_transform=None,
+ axes_class=None, axes_kwargs=None,
+ borderpad=0.5):
+ """
+ Create an anchored inset axes by scaling a parent axes. For usage, also see
+ :doc:`the examples </gallery/axes_grid1/inset_locator_demo2>`.
+
+ Parameters
+ ----------
+ parent_axes : `~matplotlib.axes.Axes`
+ Axes to place the inset axes.
+
+ zoom : float
+ Scaling factor of the data axes. *zoom* > 1 will enlarge the
+ coordinates (i.e., "zoomed in"), while *zoom* < 1 will shrink the
+ coordinates (i.e., "zoomed out").
+
+ loc : str, default: 'upper right'
+ Location to place the inset axes. Valid locations are
+ 'upper left', 'upper center', 'upper right',
+ 'center left', 'center', 'center right',
+ 'lower left', 'lower center', 'lower right'.
+ For backward compatibility, numeric values are accepted as well.
+ See the parameter *loc* of `.Legend` for details.
+
+ bbox_to_anchor : tuple or `~matplotlib.transforms.BboxBase`, optional
+ Bbox that the inset axes will be anchored to. If None,
+ *parent_axes.bbox* is used. If a tuple, can be either
+ [left, bottom, width, height], or [left, bottom].
+ If the kwargs *width* and/or *height* are specified in relative units,
+ the 2-tuple [left, bottom] cannot be used. Note that
+ the units of the bounding box are determined through the transform
+ in use. When using *bbox_to_anchor* it almost always makes sense to
+ also specify a *bbox_transform*. This might often be the axes transform
+ *parent_axes.transAxes*.
+
+ bbox_transform : `~matplotlib.transforms.Transform`, optional
+ Transformation for the bbox that contains the inset axes.
+ If None, a `.transforms.IdentityTransform` is used (i.e. pixel
+ coordinates). This is useful when not providing any argument to
+ *bbox_to_anchor*. When using *bbox_to_anchor* it almost always makes
+ sense to also specify a *bbox_transform*. This might often be the
+ axes transform *parent_axes.transAxes*. Inversely, when specifying
+ the axes- or figure-transform here, be aware that not specifying
+ *bbox_to_anchor* will use *parent_axes.bbox*, the units of which are
+ in display (pixel) coordinates.
+
+ axes_class : `~matplotlib.axes.Axes` type, default: `.HostAxes`
+ The type of the newly created inset axes.
+
+ axes_kwargs : dict, optional
+ Keyword arguments to pass to the constructor of the inset axes.
+ Valid arguments include:
+
+ %(Axes:kwdoc)s
+
+ borderpad : float, default: 0.5
+ Padding between inset axes and the bbox_to_anchor.
+ The units are axes font size, i.e. for a default font size of 10 points
+ *borderpad = 0.5* is equivalent to a padding of 5 points.
+
+ Returns
+ -------
+ inset_axes : *axes_class*
+ Inset axes object created.
+ """
+
+ return _add_inset_axes(
+ parent_axes, axes_class, axes_kwargs,
+ AnchoredZoomLocator(
+ parent_axes, zoom=zoom, loc=loc,
+ bbox_to_anchor=bbox_to_anchor, bbox_transform=bbox_transform,
+ borderpad=borderpad))
+
+
+class _TransformedBboxWithCallback(TransformedBbox):
+ """
+ Variant of `.TransformBbox` which calls *callback* before returning points.
+
+ Used by `.mark_inset` to unstale the parent axes' viewlim as needed.
+ """
+
+ def __init__(self, *args, callback, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._callback = callback
+
+ def get_points(self):
+ self._callback()
+ return super().get_points()
+
+
+@_docstring.dedent_interpd
+def mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs):
+ """
+ Draw a box to mark the location of an area represented by an inset axes.
+
+ This function draws a box in *parent_axes* at the bounding box of
+ *inset_axes*, and shows a connection with the inset axes by drawing lines
+ at the corners, giving a "zoomed in" effect.
+
+ Parameters
+ ----------
+ parent_axes : `~matplotlib.axes.Axes`
+ Axes which contains the area of the inset axes.
+
+ inset_axes : `~matplotlib.axes.Axes`
+ The inset axes.
+
+ loc1, loc2 : {1, 2, 3, 4}
+ Corners to use for connecting the inset axes and the area in the
+ parent axes.
+
+ **kwargs
+ Patch properties for the lines and box drawn:
+
+ %(Patch:kwdoc)s
+
+ Returns
+ -------
+ pp : `~matplotlib.patches.Patch`
+ The patch drawn to represent the area of the inset axes.
+
+ p1, p2 : `~matplotlib.patches.Patch`
+ The patches connecting two corners of the inset axes and its area.
+ """
+ rect = _TransformedBboxWithCallback(
+ inset_axes.viewLim, parent_axes.transData,
+ callback=parent_axes._unstale_viewLim)
+
+ kwargs.setdefault("fill", bool({'fc', 'facecolor', 'color'}.intersection(kwargs)))
+ pp = BboxPatch(rect, **kwargs)
+ parent_axes.add_patch(pp)
+
+ p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1, **kwargs)
+ inset_axes.add_patch(p1)
+ p1.set_clip_on(False)
+ p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2, **kwargs)
+ inset_axes.add_patch(p2)
+ p2.set_clip_on(False)
+
+ return pp, p1, p2
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/mpl_axes.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/mpl_axes.py
new file mode 100644
index 0000000000..51c8748758
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/mpl_axes.py
@@ -0,0 +1,128 @@
+import matplotlib.axes as maxes
+from matplotlib.artist import Artist
+from matplotlib.axis import XAxis, YAxis
+
+
+class SimpleChainedObjects:
+ def __init__(self, objects):
+ self._objects = objects
+
+ def __getattr__(self, k):
+ _a = SimpleChainedObjects([getattr(a, k) for a in self._objects])
+ return _a
+
+ def __call__(self, *args, **kwargs):
+ for m in self._objects:
+ m(*args, **kwargs)
+
+
+class Axes(maxes.Axes):
+
+ class AxisDict(dict):
+ def __init__(self, axes):
+ self.axes = axes
+ super().__init__()
+
+ def __getitem__(self, k):
+ if isinstance(k, tuple):
+ r = SimpleChainedObjects(
+ # super() within a list comprehension needs explicit args.
+ [super(Axes.AxisDict, self).__getitem__(k1) for k1 in k])
+ return r
+ elif isinstance(k, slice):
+ if k.start is None and k.stop is None and k.step is None:
+ return SimpleChainedObjects(list(self.values()))
+ else:
+ raise ValueError("Unsupported slice")
+ else:
+ return dict.__getitem__(self, k)
+
+ def __call__(self, *v, **kwargs):
+ return maxes.Axes.axis(self.axes, *v, **kwargs)
+
+ @property
+ def axis(self):
+ return self._axislines
+
+ def clear(self):
+ # docstring inherited
+ super().clear()
+ # Init axis artists.
+ self._axislines = self.AxisDict(self)
+ self._axislines.update(
+ bottom=SimpleAxisArtist(self.xaxis, 1, self.spines["bottom"]),
+ top=SimpleAxisArtist(self.xaxis, 2, self.spines["top"]),
+ left=SimpleAxisArtist(self.yaxis, 1, self.spines["left"]),
+ right=SimpleAxisArtist(self.yaxis, 2, self.spines["right"]))
+
+
+class SimpleAxisArtist(Artist):
+ def __init__(self, axis, axisnum, spine):
+ self._axis = axis
+ self._axisnum = axisnum
+ self.line = spine
+
+ if isinstance(axis, XAxis):
+ self._axis_direction = ["bottom", "top"][axisnum-1]
+ elif isinstance(axis, YAxis):
+ self._axis_direction = ["left", "right"][axisnum-1]
+ else:
+ raise ValueError(
+ f"axis must be instance of XAxis or YAxis, but got {axis}")
+ super().__init__()
+
+ @property
+ def major_ticks(self):
+ tickline = "tick%dline" % self._axisnum
+ return SimpleChainedObjects([getattr(tick, tickline)
+ for tick in self._axis.get_major_ticks()])
+
+ @property
+ def major_ticklabels(self):
+ label = "label%d" % self._axisnum
+ return SimpleChainedObjects([getattr(tick, label)
+ for tick in self._axis.get_major_ticks()])
+
+ @property
+ def label(self):
+ return self._axis.label
+
+ def set_visible(self, b):
+ self.toggle(all=b)
+ self.line.set_visible(b)
+ self._axis.set_visible(True)
+ super().set_visible(b)
+
+ def set_label(self, txt):
+ self._axis.set_label_text(txt)
+
+ def toggle(self, all=None, ticks=None, ticklabels=None, label=None):
+
+ if all:
+ _ticks, _ticklabels, _label = True, True, True
+ elif all is not None:
+ _ticks, _ticklabels, _label = False, False, False
+ else:
+ _ticks, _ticklabels, _label = None, None, None
+
+ if ticks is not None:
+ _ticks = ticks
+ if ticklabels is not None:
+ _ticklabels = ticklabels
+ if label is not None:
+ _label = label
+
+ if _ticks is not None:
+ tickparam = {f"tick{self._axisnum}On": _ticks}
+ self._axis.set_tick_params(**tickparam)
+ if _ticklabels is not None:
+ tickparam = {f"label{self._axisnum}On": _ticklabels}
+ self._axis.set_tick_params(**tickparam)
+
+ if _label is not None:
+ pos = self._axis.get_label_position()
+ if (pos == self._axis_direction) and not _label:
+ self._axis.label.set_visible(False)
+ elif _label:
+ self._axis.label.set_visible(True)
+ self._axis.set_label_position(self._axis_direction)
diff --git a/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/parasite_axes.py b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/parasite_axes.py
new file mode 100644
index 0000000000..2a2b5957e8
--- /dev/null
+++ b/contrib/python/matplotlib/py3/mpl_toolkits/axes_grid1/parasite_axes.py
@@ -0,0 +1,257 @@
+from matplotlib import _api, cbook
+import matplotlib.artist as martist
+import matplotlib.transforms as mtransforms
+from matplotlib.transforms import Bbox
+from .mpl_axes import Axes
+
+
+class ParasiteAxesBase:
+
+ def __init__(self, parent_axes, aux_transform=None,
+ *, viewlim_mode=None, **kwargs):
+ self._parent_axes = parent_axes
+ self.transAux = aux_transform
+ self.set_viewlim_mode(viewlim_mode)
+ kwargs["frameon"] = False
+ super().__init__(parent_axes.figure, parent_axes._position, **kwargs)
+
+ def clear(self):
+ super().clear()
+ martist.setp(self.get_children(), visible=False)
+ self._get_lines = self._parent_axes._get_lines
+ self._parent_axes.callbacks._connect_picklable(
+ "xlim_changed", self._sync_lims)
+ self._parent_axes.callbacks._connect_picklable(
+ "ylim_changed", self._sync_lims)
+
+ def pick(self, mouseevent):
+ # This most likely goes to Artist.pick (depending on axes_class given
+ # to the factory), which only handles pick events registered on the
+ # axes associated with each child:
+ super().pick(mouseevent)
+ # But parasite axes are additionally given pick events from their host
+ # axes (cf. HostAxesBase.pick), which we handle here:
+ for a in self.get_children():
+ if (hasattr(mouseevent.inaxes, "parasites")
+ and self in mouseevent.inaxes.parasites):
+ a.pick(mouseevent)
+
+ # aux_transform support
+
+ def _set_lim_and_transforms(self):
+ if self.transAux is not None:
+ self.transAxes = self._parent_axes.transAxes
+ self.transData = self.transAux + self._parent_axes.transData
+ self._xaxis_transform = mtransforms.blended_transform_factory(
+ self.transData, self.transAxes)
+ self._yaxis_transform = mtransforms.blended_transform_factory(
+ self.transAxes, self.transData)
+ else:
+ super()._set_lim_and_transforms()
+
+ def set_viewlim_mode(self, mode):
+ _api.check_in_list([None, "equal", "transform"], mode=mode)
+ self._viewlim_mode = mode
+
+ def get_viewlim_mode(self):
+ return self._viewlim_mode
+
+ def _sync_lims(self, parent):
+ viewlim = parent.viewLim.frozen()
+ mode = self.get_viewlim_mode()
+ if mode is None:
+ pass
+ elif mode == "equal":
+ self.viewLim.set(viewlim)
+ elif mode == "transform":
+ self.viewLim.set(viewlim.transformed(self.transAux.inverted()))
+ else:
+ _api.check_in_list([None, "equal", "transform"], mode=mode)
+
+ # end of aux_transform support
+
+
+parasite_axes_class_factory = cbook._make_class_factory(
+ ParasiteAxesBase, "{}Parasite")
+ParasiteAxes = parasite_axes_class_factory(Axes)
+
+
+class HostAxesBase:
+ def __init__(self, *args, **kwargs):
+ self.parasites = []
+ super().__init__(*args, **kwargs)
+
+ def get_aux_axes(
+ self, tr=None, viewlim_mode="equal", axes_class=None, **kwargs):
+ """
+ Add a parasite axes to this host.
+
+ Despite this method's name, this should actually be thought of as an
+ ``add_parasite_axes`` method.
+
+ .. versionchanged:: 3.7
+ Defaults to same base axes class as host axes.
+
+ Parameters
+ ----------
+ tr : `~matplotlib.transforms.Transform` or None, default: None
+ If a `.Transform`, the following relation will hold:
+ ``parasite.transData = tr + host.transData``.
+ If None, the parasite's and the host's ``transData`` are unrelated.
+ viewlim_mode : {"equal", "transform", None}, default: "equal"
+ How the parasite's view limits are set: directly equal to the
+ parent axes ("equal"), equal after application of *tr*
+ ("transform"), or independently (None).
+ axes_class : subclass type of `~matplotlib.axes.Axes`, optional
+ The `~.axes.Axes` subclass that is instantiated. If None, the base
+ class of the host axes is used.
+ **kwargs
+ Other parameters are forwarded to the parasite axes constructor.
+ """
+ if axes_class is None:
+ axes_class = self._base_axes_class
+ parasite_axes_class = parasite_axes_class_factory(axes_class)
+ ax2 = parasite_axes_class(
+ self, tr, viewlim_mode=viewlim_mode, **kwargs)
+ # note that ax2.transData == tr + ax1.transData
+ # Anything you draw in ax2 will match the ticks and grids of ax1.
+ self.parasites.append(ax2)
+ ax2._remove_method = self.parasites.remove
+ return ax2
+
+ def draw(self, renderer):
+ orig_children_len = len(self._children)
+
+ locator = self.get_axes_locator()
+ if locator:
+ pos = locator(self, renderer)
+ self.set_position(pos, which="active")
+ self.apply_aspect(pos)
+ else:
+ self.apply_aspect()
+
+ rect = self.get_position()
+ for ax in self.parasites:
+ ax.apply_aspect(rect)
+ self._children.extend(ax.get_children())
+
+ super().draw(renderer)
+ del self._children[orig_children_len:]
+
+ def clear(self):
+ super().clear()
+ for ax in self.parasites:
+ ax.clear()
+
+ def pick(self, mouseevent):
+ super().pick(mouseevent)
+ # Also pass pick events on to parasite axes and, in turn, their
+ # children (cf. ParasiteAxesBase.pick)
+ for a in self.parasites:
+ a.pick(mouseevent)
+
+ def twinx(self, axes_class=None):
+ """
+ Create a twin of Axes with a shared x-axis but independent y-axis.
+
+ The y-axis of self will have ticks on the left and the returned axes
+ will have ticks on the right.
+ """
+ ax = self._add_twin_axes(axes_class, sharex=self)
+ self.axis["right"].set_visible(False)
+ ax.axis["right"].set_visible(True)
+ ax.axis["left", "top", "bottom"].set_visible(False)
+ return ax
+
+ def twiny(self, axes_class=None):
+ """
+ Create a twin of Axes with a shared y-axis but independent x-axis.
+
+ The x-axis of self will have ticks on the bottom and the returned axes
+ will have ticks on the top.
+ """
+ ax = self._add_twin_axes(axes_class, sharey=self)
+ self.axis["top"].set_visible(False)
+ ax.axis["top"].set_visible(True)
+ ax.axis["left", "right", "bottom"].set_visible(False)
+ return ax
+
+ def twin(self, aux_trans=None, axes_class=None):
+ """
+ Create a twin of Axes with no shared axis.
+
+ While self will have ticks on the left and bottom axis, the returned
+ axes will have ticks on the top and right axis.
+ """
+ if aux_trans is None:
+ aux_trans = mtransforms.IdentityTransform()
+ ax = self._add_twin_axes(
+ axes_class, aux_transform=aux_trans, viewlim_mode="transform")
+ self.axis["top", "right"].set_visible(False)
+ ax.axis["top", "right"].set_visible(True)
+ ax.axis["left", "bottom"].set_visible(False)
+ return ax
+
+ def _add_twin_axes(self, axes_class, **kwargs):
+ """
+ Helper for `.twinx`/`.twiny`/`.twin`.
+
+ *kwargs* are forwarded to the parasite axes constructor.
+ """
+ if axes_class is None:
+ axes_class = self._base_axes_class
+ ax = parasite_axes_class_factory(axes_class)(self, **kwargs)
+ self.parasites.append(ax)
+ ax._remove_method = self._remove_any_twin
+ return ax
+
+ def _remove_any_twin(self, ax):
+ self.parasites.remove(ax)
+ restore = ["top", "right"]
+ if ax._sharex:
+ restore.remove("top")
+ if ax._sharey:
+ restore.remove("right")
+ self.axis[tuple(restore)].set_visible(True)
+ self.axis[tuple(restore)].toggle(ticklabels=False, label=False)
+
+ @_api.make_keyword_only("3.8", "call_axes_locator")
+ def get_tightbbox(self, renderer=None, call_axes_locator=True,
+ bbox_extra_artists=None):
+ bbs = [
+ *[ax.get_tightbbox(renderer, call_axes_locator=call_axes_locator)
+ for ax in self.parasites],
+ super().get_tightbbox(renderer,
+ call_axes_locator=call_axes_locator,
+ bbox_extra_artists=bbox_extra_artists)]
+ return Bbox.union([b for b in bbs if b.width != 0 or b.height != 0])
+
+
+host_axes_class_factory = host_subplot_class_factory = \
+ cbook._make_class_factory(HostAxesBase, "{}HostAxes", "_base_axes_class")
+HostAxes = SubplotHost = host_axes_class_factory(Axes)
+
+
+def host_axes(*args, axes_class=Axes, figure=None, **kwargs):
+ """
+ Create axes that can act as a hosts to parasitic axes.
+
+ Parameters
+ ----------
+ figure : `~matplotlib.figure.Figure`
+ Figure to which the axes will be added. Defaults to the current figure
+ `.pyplot.gcf()`.
+
+ *args, **kwargs
+ Will be passed on to the underlying `~.axes.Axes` object creation.
+ """
+ import matplotlib.pyplot as plt
+ host_axes_class = host_axes_class_factory(axes_class)
+ if figure is None:
+ figure = plt.gcf()
+ ax = host_axes_class(figure, *args, **kwargs)
+ figure.add_axes(ax)
+ return ax
+
+
+host_subplot = host_axes