reconnect moved files to git repo
This commit is contained in:
@ -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"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,414 @@
|
||||
from matplotlib import transforms
|
||||
from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox,
|
||||
DrawingArea, TextArea, VPacker)
|
||||
from matplotlib.patches import (Rectangle, ArrowStyle,
|
||||
FancyArrowPatch, PathPatch)
|
||||
from matplotlib.text import TextPath
|
||||
|
||||
__all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox',
|
||||
'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)
|
||||
|
||||
|
||||
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)
|
||||
@ -0,0 +1,618 @@
|
||||
"""
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
@ -0,0 +1,563 @@
|
||||
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.get_figure(root=False).colorbar(
|
||||
mappable, cax=self, location=self.orientation, **kwargs)
|
||||
|
||||
|
||||
_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.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
axes_all : list of Axes
|
||||
A flat list of Axes. Note that you can also access this directly
|
||||
from the grid. The following is equivalent ::
|
||||
|
||||
grid[i] == grid.axes_all[i]
|
||||
len(grid) == len(grid.axes_all)
|
||||
|
||||
axes_column : list of list of Axes
|
||||
A 2D list of Axes where the first index is the column. This results
|
||||
in the usage pattern ``grid.axes_column[col][row]``.
|
||||
axes_row : list of list of Axes
|
||||
A 2D list of Axes where the first index is the row. This results
|
||||
in the usage pattern ``grid.axes_row[row][col]``.
|
||||
axes_llc : Axes
|
||||
The Axes in the lower left corner.
|
||||
ngrids : int
|
||||
Number of Axes in the grid.
|
||||
"""
|
||||
|
||||
_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: `.mpl_axes.Axes`
|
||||
The type of Axes to create.
|
||||
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.
|
||||
"""
|
||||
_api.check_in_list(["all", "L", "1", "keep"], mode=mode)
|
||||
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:
|
||||
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.
|
||||
|
||||
.. versionchanged:: 3.10
|
||||
``cbar_mode="single"`` no longer adds *axes_pad* between the axes
|
||||
and the colorbar if the *cbar_location* is "left" or "bottom".
|
||||
|
||||
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].get_figure(root=False), 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 col != 0:
|
||||
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 row != 0:
|
||||
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
|
||||
@ -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
|
||||
@ -0,0 +1,271 @@
|
||||
"""
|
||||
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.
|
||||
|
||||
Once created, the unit classes can be modified by simple arithmetic
|
||||
operations: addition /subtraction with another unit type or a real number and scaling
|
||||
(multiplication or division) by a real number.
|
||||
"""
|
||||
|
||||
from numbers import Real
|
||||
|
||||
from matplotlib import _api
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
|
||||
class _Base:
|
||||
def __rmul__(self, other):
|
||||
return self * other
|
||||
|
||||
def __mul__(self, other):
|
||||
if not isinstance(other, Real):
|
||||
return NotImplemented
|
||||
return Fraction(other, self)
|
||||
|
||||
def __div__(self, other):
|
||||
return (1 / other) * self
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, _Base):
|
||||
return Add(self, other)
|
||||
else:
|
||||
return Add(self, Fixed(other))
|
||||
|
||||
def __neg__(self):
|
||||
return -1 * self
|
||||
|
||||
def __radd__(self, other):
|
||||
# other cannot be a _Base instance, because A + B would trigger
|
||||
# A.__add__(B) first.
|
||||
return Add(self, Fixed(other))
|
||||
|
||||
def __sub__(self, other):
|
||||
return self + (-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):
|
||||
_api.check_in_list(self._get_size_map, direction=direction)
|
||||
self._direction = direction
|
||||
self._ax_list = [ax] if isinstance(ax, Axes) else ax
|
||||
|
||||
def get_size(self, renderer):
|
||||
sz = max([
|
||||
self._get_size_map[self._direction](
|
||||
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
|
||||
@ -0,0 +1,519 @@
|
||||
"""
|
||||
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
|
||||
from matplotlib.transforms import IdentityTransform, TransformedBbox
|
||||
|
||||
from . import axes_size as Size
|
||||
from .parasite_axes import HostAxes
|
||||
|
||||
|
||||
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):
|
||||
fig = ax.get_figure(root=False)
|
||||
if renderer is None:
|
||||
renderer = fig._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 = fig.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.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.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.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 = {}
|
||||
fig = parent_axes.get_figure(root=False)
|
||||
inset_axes = axes_class(
|
||||
fig, parent_axes.get_position(),
|
||||
**{"navigate": False, **axes_kwargs, "axes_locator": axes_locator})
|
||||
return fig.add_axes(inset_axes)
|
||||
|
||||
|
||||
@_docstring.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.get_figure(root=False).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.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.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
|
||||
@ -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)
|
||||
@ -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.get_figure(root=False),
|
||||
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)
|
||||
|
||||
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
|
||||
@ -0,0 +1,10 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Check that the test directories exist
|
||||
if not (Path(__file__).parent / "baseline_images").exists():
|
||||
raise OSError(
|
||||
'The baseline image directory does not exist. '
|
||||
'This is most likely because the test data is not installed. '
|
||||
'You may need to install matplotlib from source to get the '
|
||||
'test data.')
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
|
||||
from matplotlib.testing.conftest import (mpl_test_settings, # noqa
|
||||
pytest_configure, pytest_unconfigure)
|
||||
@ -0,0 +1,782 @@
|
||||
from itertools import product
|
||||
import io
|
||||
import platform
|
||||
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.ticker as mticker
|
||||
from matplotlib import cbook
|
||||
from matplotlib.backend_bases import MouseEvent
|
||||
from matplotlib.colors import LogNorm
|
||||
from matplotlib.patches import Circle, Ellipse
|
||||
from matplotlib.transforms import Bbox, TransformedBbox
|
||||
from matplotlib.testing.decorators import (
|
||||
check_figures_equal, image_comparison, remove_ticks_and_titles)
|
||||
|
||||
from mpl_toolkits.axes_grid1 import (
|
||||
axes_size as Size,
|
||||
host_subplot, make_axes_locatable,
|
||||
Grid, AxesGrid, ImageGrid)
|
||||
from mpl_toolkits.axes_grid1.anchored_artists import (
|
||||
AnchoredAuxTransformBox, AnchoredDrawingArea,
|
||||
AnchoredDirectionArrows, AnchoredSizeBar)
|
||||
from mpl_toolkits.axes_grid1.axes_divider import (
|
||||
Divider, HBoxDivider, make_axes_area_auto_adjustable, SubplotDivider,
|
||||
VBoxDivider)
|
||||
from mpl_toolkits.axes_grid1.axes_rgb import RGBAxes
|
||||
from mpl_toolkits.axes_grid1.inset_locator import (
|
||||
zoomed_inset_axes, mark_inset, inset_axes, BboxConnectorPatch)
|
||||
import mpl_toolkits.axes_grid1.mpl_axes
|
||||
import pytest
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_equal, assert_array_almost_equal
|
||||
|
||||
|
||||
def test_divider_append_axes():
|
||||
fig, ax = plt.subplots()
|
||||
divider = make_axes_locatable(ax)
|
||||
axs = {
|
||||
"main": ax,
|
||||
"top": divider.append_axes("top", 1.2, pad=0.1, sharex=ax),
|
||||
"bottom": divider.append_axes("bottom", 1.2, pad=0.1, sharex=ax),
|
||||
"left": divider.append_axes("left", 1.2, pad=0.1, sharey=ax),
|
||||
"right": divider.append_axes("right", 1.2, pad=0.1, sharey=ax),
|
||||
}
|
||||
fig.canvas.draw()
|
||||
bboxes = {k: axs[k].get_window_extent() for k in axs}
|
||||
dpi = fig.dpi
|
||||
assert bboxes["top"].height == pytest.approx(1.2 * dpi)
|
||||
assert bboxes["bottom"].height == pytest.approx(1.2 * dpi)
|
||||
assert bboxes["left"].width == pytest.approx(1.2 * dpi)
|
||||
assert bboxes["right"].width == pytest.approx(1.2 * dpi)
|
||||
assert bboxes["top"].y0 - bboxes["main"].y1 == pytest.approx(0.1 * dpi)
|
||||
assert bboxes["main"].y0 - bboxes["bottom"].y1 == pytest.approx(0.1 * dpi)
|
||||
assert bboxes["main"].x0 - bboxes["left"].x1 == pytest.approx(0.1 * dpi)
|
||||
assert bboxes["right"].x0 - bboxes["main"].x1 == pytest.approx(0.1 * dpi)
|
||||
assert bboxes["left"].y0 == bboxes["main"].y0 == bboxes["right"].y0
|
||||
assert bboxes["left"].y1 == bboxes["main"].y1 == bboxes["right"].y1
|
||||
assert bboxes["top"].x0 == bboxes["main"].x0 == bboxes["bottom"].x0
|
||||
assert bboxes["top"].x1 == bboxes["main"].x1 == bboxes["bottom"].x1
|
||||
|
||||
|
||||
# Update style when regenerating the test image
|
||||
@image_comparison(['twin_axes_empty_and_removed'], extensions=["png"], tol=1,
|
||||
style=('classic', '_classic_test_patch'))
|
||||
def test_twin_axes_empty_and_removed():
|
||||
# Purely cosmetic font changes (avoid overlap)
|
||||
mpl.rcParams.update(
|
||||
{"font.size": 8, "xtick.labelsize": 8, "ytick.labelsize": 8})
|
||||
generators = ["twinx", "twiny", "twin"]
|
||||
modifiers = ["", "host invisible", "twin removed", "twin invisible",
|
||||
"twin removed\nhost invisible"]
|
||||
# Unmodified host subplot at the beginning for reference
|
||||
h = host_subplot(len(modifiers)+1, len(generators), 2)
|
||||
h.text(0.5, 0.5, "host_subplot",
|
||||
horizontalalignment="center", verticalalignment="center")
|
||||
# Host subplots with various modifications (twin*, visibility) applied
|
||||
for i, (mod, gen) in enumerate(product(modifiers, generators),
|
||||
len(generators) + 1):
|
||||
h = host_subplot(len(modifiers)+1, len(generators), i)
|
||||
t = getattr(h, gen)()
|
||||
if "twin invisible" in mod:
|
||||
t.axis[:].set_visible(False)
|
||||
if "twin removed" in mod:
|
||||
t.remove()
|
||||
if "host invisible" in mod:
|
||||
h.axis[:].set_visible(False)
|
||||
h.text(0.5, 0.5, gen + ("\n" + mod if mod else ""),
|
||||
horizontalalignment="center", verticalalignment="center")
|
||||
plt.subplots_adjust(wspace=0.5, hspace=1)
|
||||
|
||||
|
||||
def test_twin_axes_both_with_units():
|
||||
host = host_subplot(111)
|
||||
with pytest.warns(mpl.MatplotlibDeprecationWarning):
|
||||
host.plot_date([0, 1, 2], [0, 1, 2], xdate=False, ydate=True)
|
||||
twin = host.twinx()
|
||||
twin.plot(["a", "b", "c"])
|
||||
assert host.get_yticklabels()[0].get_text() == "00:00:00"
|
||||
assert twin.get_yticklabels()[0].get_text() == "a"
|
||||
|
||||
|
||||
def test_axesgrid_colorbar_log_smoketest():
|
||||
fig = plt.figure()
|
||||
grid = AxesGrid(fig, 111, # modified to be only subplot
|
||||
nrows_ncols=(1, 1),
|
||||
ngrids=1,
|
||||
label_mode="L",
|
||||
cbar_location="top",
|
||||
cbar_mode="single",
|
||||
)
|
||||
|
||||
Z = 10000 * np.random.rand(10, 10)
|
||||
im = grid[0].imshow(Z, interpolation="nearest", norm=LogNorm())
|
||||
|
||||
grid.cbar_axes[0].colorbar(im)
|
||||
|
||||
|
||||
def test_inset_colorbar_tight_layout_smoketest():
|
||||
fig, ax = plt.subplots(1, 1)
|
||||
pts = ax.scatter([0, 1], [0, 1], c=[1, 5])
|
||||
|
||||
cax = inset_axes(ax, width="3%", height="70%")
|
||||
plt.colorbar(pts, cax=cax)
|
||||
|
||||
with pytest.warns(UserWarning, match="This figure includes Axes"):
|
||||
# Will warn, but not raise an error
|
||||
plt.tight_layout()
|
||||
|
||||
|
||||
@image_comparison(['inset_locator.png'], style='default', remove_text=True)
|
||||
def test_inset_locator():
|
||||
fig, ax = plt.subplots(figsize=[5, 4])
|
||||
|
||||
# prepare the demo image
|
||||
# Z is a 15x15 array
|
||||
Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy")
|
||||
extent = (-3, 4, -4, 3)
|
||||
Z2 = np.zeros((150, 150))
|
||||
ny, nx = Z.shape
|
||||
Z2[30:30+ny, 30:30+nx] = Z
|
||||
|
||||
ax.imshow(Z2, extent=extent, interpolation="nearest",
|
||||
origin="lower")
|
||||
|
||||
axins = zoomed_inset_axes(ax, zoom=6, loc='upper right')
|
||||
axins.imshow(Z2, extent=extent, interpolation="nearest",
|
||||
origin="lower")
|
||||
axins.yaxis.get_major_locator().set_params(nbins=7)
|
||||
axins.xaxis.get_major_locator().set_params(nbins=7)
|
||||
# sub region of the original image
|
||||
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
|
||||
axins.set_xlim(x1, x2)
|
||||
axins.set_ylim(y1, y2)
|
||||
|
||||
plt.xticks(visible=False)
|
||||
plt.yticks(visible=False)
|
||||
|
||||
# draw a bbox of the region of the inset axes in the parent axes and
|
||||
# connecting lines between the bbox and the inset axes area
|
||||
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
|
||||
|
||||
asb = AnchoredSizeBar(ax.transData,
|
||||
0.5,
|
||||
'0.5',
|
||||
loc='lower center',
|
||||
pad=0.1, borderpad=0.5, sep=5,
|
||||
frameon=False)
|
||||
ax.add_artist(asb)
|
||||
|
||||
|
||||
@image_comparison(['inset_axes.png'], style='default', remove_text=True)
|
||||
def test_inset_axes():
|
||||
fig, ax = plt.subplots(figsize=[5, 4])
|
||||
|
||||
# prepare the demo image
|
||||
# Z is a 15x15 array
|
||||
Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy")
|
||||
extent = (-3, 4, -4, 3)
|
||||
Z2 = np.zeros((150, 150))
|
||||
ny, nx = Z.shape
|
||||
Z2[30:30+ny, 30:30+nx] = Z
|
||||
|
||||
ax.imshow(Z2, extent=extent, interpolation="nearest",
|
||||
origin="lower")
|
||||
|
||||
# creating our inset axes with a bbox_transform parameter
|
||||
axins = inset_axes(ax, width=1., height=1., bbox_to_anchor=(1, 1),
|
||||
bbox_transform=ax.transAxes)
|
||||
|
||||
axins.imshow(Z2, extent=extent, interpolation="nearest",
|
||||
origin="lower")
|
||||
axins.yaxis.get_major_locator().set_params(nbins=7)
|
||||
axins.xaxis.get_major_locator().set_params(nbins=7)
|
||||
# sub region of the original image
|
||||
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
|
||||
axins.set_xlim(x1, x2)
|
||||
axins.set_ylim(y1, y2)
|
||||
|
||||
plt.xticks(visible=False)
|
||||
plt.yticks(visible=False)
|
||||
|
||||
# draw a bbox of the region of the inset axes in the parent axes and
|
||||
# connecting lines between the bbox and the inset axes area
|
||||
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
|
||||
|
||||
asb = AnchoredSizeBar(ax.transData,
|
||||
0.5,
|
||||
'0.5',
|
||||
loc='lower center',
|
||||
pad=0.1, borderpad=0.5, sep=5,
|
||||
frameon=False)
|
||||
ax.add_artist(asb)
|
||||
|
||||
|
||||
def test_inset_axes_complete():
|
||||
dpi = 100
|
||||
figsize = (6, 5)
|
||||
fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
|
||||
fig.subplots_adjust(.1, .1, .9, .9)
|
||||
|
||||
ins = inset_axes(ax, width=2., height=2., borderpad=0)
|
||||
fig.canvas.draw()
|
||||
assert_array_almost_equal(
|
||||
ins.get_position().extents,
|
||||
[(0.9*figsize[0]-2.)/figsize[0], (0.9*figsize[1]-2.)/figsize[1],
|
||||
0.9, 0.9])
|
||||
|
||||
ins = inset_axes(ax, width="40%", height="30%", borderpad=0)
|
||||
fig.canvas.draw()
|
||||
assert_array_almost_equal(
|
||||
ins.get_position().extents, [.9-.8*.4, .9-.8*.3, 0.9, 0.9])
|
||||
|
||||
ins = inset_axes(ax, width=1., height=1.2, bbox_to_anchor=(200, 100),
|
||||
loc=3, borderpad=0)
|
||||
fig.canvas.draw()
|
||||
assert_array_almost_equal(
|
||||
ins.get_position().extents,
|
||||
[200/dpi/figsize[0], 100/dpi/figsize[1],
|
||||
(200/dpi+1)/figsize[0], (100/dpi+1.2)/figsize[1]])
|
||||
|
||||
ins1 = inset_axes(ax, width="35%", height="60%", loc=3, borderpad=1)
|
||||
ins2 = inset_axes(ax, width="100%", height="100%",
|
||||
bbox_to_anchor=(0, 0, .35, .60),
|
||||
bbox_transform=ax.transAxes, loc=3, borderpad=1)
|
||||
fig.canvas.draw()
|
||||
assert_array_equal(ins1.get_position().extents,
|
||||
ins2.get_position().extents)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ins = inset_axes(ax, width="40%", height="30%",
|
||||
bbox_to_anchor=(0.4, 0.5))
|
||||
|
||||
with pytest.warns(UserWarning):
|
||||
ins = inset_axes(ax, width="40%", height="30%",
|
||||
bbox_transform=ax.transAxes)
|
||||
|
||||
|
||||
def test_inset_axes_tight():
|
||||
# gh-26287 found that inset_axes raised with bbox_inches=tight
|
||||
fig, ax = plt.subplots()
|
||||
inset_axes(ax, width=1.3, height=0.9)
|
||||
|
||||
f = io.BytesIO()
|
||||
fig.savefig(f, bbox_inches="tight")
|
||||
|
||||
|
||||
@image_comparison(['fill_facecolor.png'], remove_text=True, style='mpl20')
|
||||
def test_fill_facecolor():
|
||||
fig, ax = plt.subplots(1, 5)
|
||||
fig.set_size_inches(5, 5)
|
||||
for i in range(1, 4):
|
||||
ax[i].yaxis.set_visible(False)
|
||||
ax[4].yaxis.tick_right()
|
||||
bbox = Bbox.from_extents(0, 0.4, 1, 0.6)
|
||||
|
||||
# fill with blue by setting 'fc' field
|
||||
bbox1 = TransformedBbox(bbox, ax[0].transData)
|
||||
bbox2 = TransformedBbox(bbox, ax[1].transData)
|
||||
# set color to BboxConnectorPatch
|
||||
p = BboxConnectorPatch(
|
||||
bbox1, bbox2, loc1a=1, loc2a=2, loc1b=4, loc2b=3,
|
||||
ec="r", fc="b")
|
||||
p.set_clip_on(False)
|
||||
ax[0].add_patch(p)
|
||||
# set color to marked area
|
||||
axins = zoomed_inset_axes(ax[0], 1, loc='upper right')
|
||||
axins.set_xlim(0, 0.2)
|
||||
axins.set_ylim(0, 0.2)
|
||||
plt.gca().axes.xaxis.set_ticks([])
|
||||
plt.gca().axes.yaxis.set_ticks([])
|
||||
mark_inset(ax[0], axins, loc1=2, loc2=4, fc="b", ec="0.5")
|
||||
|
||||
# fill with yellow by setting 'facecolor' field
|
||||
bbox3 = TransformedBbox(bbox, ax[1].transData)
|
||||
bbox4 = TransformedBbox(bbox, ax[2].transData)
|
||||
# set color to BboxConnectorPatch
|
||||
p = BboxConnectorPatch(
|
||||
bbox3, bbox4, loc1a=1, loc2a=2, loc1b=4, loc2b=3,
|
||||
ec="r", facecolor="y")
|
||||
p.set_clip_on(False)
|
||||
ax[1].add_patch(p)
|
||||
# set color to marked area
|
||||
axins = zoomed_inset_axes(ax[1], 1, loc='upper right')
|
||||
axins.set_xlim(0, 0.2)
|
||||
axins.set_ylim(0, 0.2)
|
||||
plt.gca().axes.xaxis.set_ticks([])
|
||||
plt.gca().axes.yaxis.set_ticks([])
|
||||
mark_inset(ax[1], axins, loc1=2, loc2=4, facecolor="y", ec="0.5")
|
||||
|
||||
# fill with green by setting 'color' field
|
||||
bbox5 = TransformedBbox(bbox, ax[2].transData)
|
||||
bbox6 = TransformedBbox(bbox, ax[3].transData)
|
||||
# set color to BboxConnectorPatch
|
||||
p = BboxConnectorPatch(
|
||||
bbox5, bbox6, loc1a=1, loc2a=2, loc1b=4, loc2b=3,
|
||||
ec="r", color="g")
|
||||
p.set_clip_on(False)
|
||||
ax[2].add_patch(p)
|
||||
# set color to marked area
|
||||
axins = zoomed_inset_axes(ax[2], 1, loc='upper right')
|
||||
axins.set_xlim(0, 0.2)
|
||||
axins.set_ylim(0, 0.2)
|
||||
plt.gca().axes.xaxis.set_ticks([])
|
||||
plt.gca().axes.yaxis.set_ticks([])
|
||||
mark_inset(ax[2], axins, loc1=2, loc2=4, color="g", ec="0.5")
|
||||
|
||||
# fill with green but color won't show if set fill to False
|
||||
bbox7 = TransformedBbox(bbox, ax[3].transData)
|
||||
bbox8 = TransformedBbox(bbox, ax[4].transData)
|
||||
# BboxConnectorPatch won't show green
|
||||
p = BboxConnectorPatch(
|
||||
bbox7, bbox8, loc1a=1, loc2a=2, loc1b=4, loc2b=3,
|
||||
ec="r", fc="g", fill=False)
|
||||
p.set_clip_on(False)
|
||||
ax[3].add_patch(p)
|
||||
# marked area won't show green
|
||||
axins = zoomed_inset_axes(ax[3], 1, loc='upper right')
|
||||
axins.set_xlim(0, 0.2)
|
||||
axins.set_ylim(0, 0.2)
|
||||
axins.xaxis.set_ticks([])
|
||||
axins.yaxis.set_ticks([])
|
||||
mark_inset(ax[3], axins, loc1=2, loc2=4, fc="g", ec="0.5", fill=False)
|
||||
|
||||
|
||||
# Update style when regenerating the test image
|
||||
@image_comparison(['zoomed_axes.png', 'inverted_zoomed_axes.png'],
|
||||
style=('classic', '_classic_test_patch'),
|
||||
tol=0 if platform.machine() == 'x86_64' else 0.02)
|
||||
def test_zooming_with_inverted_axes():
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot([1, 2, 3], [1, 2, 3])
|
||||
ax.axis([1, 3, 1, 3])
|
||||
inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc='lower right')
|
||||
inset_ax.axis([1.1, 1.4, 1.1, 1.4])
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
ax.plot([1, 2, 3], [1, 2, 3])
|
||||
ax.axis([3, 1, 3, 1])
|
||||
inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc='lower right')
|
||||
inset_ax.axis([1.4, 1.1, 1.4, 1.1])
|
||||
|
||||
|
||||
# Update style when regenerating the test image
|
||||
@image_comparison(['anchored_direction_arrows.png'],
|
||||
tol=0 if platform.machine() == 'x86_64' else 0.01,
|
||||
style=('classic', '_classic_test_patch'))
|
||||
def test_anchored_direction_arrows():
|
||||
fig, ax = plt.subplots()
|
||||
ax.imshow(np.zeros((10, 10)), interpolation='nearest')
|
||||
|
||||
simple_arrow = AnchoredDirectionArrows(ax.transAxes, 'X', 'Y')
|
||||
ax.add_artist(simple_arrow)
|
||||
|
||||
|
||||
# Update style when regenerating the test image
|
||||
@image_comparison(['anchored_direction_arrows_many_args.png'],
|
||||
style=('classic', '_classic_test_patch'))
|
||||
def test_anchored_direction_arrows_many_args():
|
||||
fig, ax = plt.subplots()
|
||||
ax.imshow(np.ones((10, 10)))
|
||||
|
||||
direction_arrows = AnchoredDirectionArrows(
|
||||
ax.transAxes, 'A', 'B', loc='upper right', color='red',
|
||||
aspect_ratio=-0.5, pad=0.6, borderpad=2, frameon=True, alpha=0.7,
|
||||
sep_x=-0.06, sep_y=-0.08, back_length=0.1, head_width=9,
|
||||
head_length=10, tail_width=5)
|
||||
ax.add_artist(direction_arrows)
|
||||
|
||||
|
||||
def test_axes_locatable_position():
|
||||
fig, ax = plt.subplots()
|
||||
divider = make_axes_locatable(ax)
|
||||
with mpl.rc_context({"figure.subplot.wspace": 0.02}):
|
||||
cax = divider.append_axes('right', size='5%')
|
||||
fig.canvas.draw()
|
||||
assert np.isclose(cax.get_position(original=False).width,
|
||||
0.03621495327102808)
|
||||
|
||||
|
||||
@image_comparison(['image_grid_each_left_label_mode_all.png'], style='mpl20',
|
||||
savefig_kwarg={'bbox_inches': 'tight'})
|
||||
def test_image_grid_each_left_label_mode_all():
|
||||
imdata = np.arange(100).reshape((10, 10))
|
||||
|
||||
fig = plt.figure(1, (3, 3))
|
||||
grid = ImageGrid(fig, (1, 1, 1), nrows_ncols=(3, 2), axes_pad=(0.5, 0.3),
|
||||
cbar_mode="each", cbar_location="left", cbar_size="15%",
|
||||
label_mode="all")
|
||||
# 3-tuple rect => SubplotDivider
|
||||
assert isinstance(grid.get_divider(), SubplotDivider)
|
||||
assert grid.get_axes_pad() == (0.5, 0.3)
|
||||
assert grid.get_aspect() # True by default for ImageGrid
|
||||
for ax, cax in zip(grid, grid.cbar_axes):
|
||||
im = ax.imshow(imdata, interpolation='none')
|
||||
cax.colorbar(im)
|
||||
|
||||
|
||||
@image_comparison(['image_grid_single_bottom_label_mode_1.png'], style='mpl20',
|
||||
savefig_kwarg={'bbox_inches': 'tight'})
|
||||
def test_image_grid_single_bottom():
|
||||
imdata = np.arange(100).reshape((10, 10))
|
||||
|
||||
fig = plt.figure(1, (2.5, 1.5))
|
||||
grid = ImageGrid(fig, (0, 0, 1, 1), nrows_ncols=(1, 3),
|
||||
axes_pad=(0.2, 0.15), cbar_mode="single", cbar_pad=0.3,
|
||||
cbar_location="bottom", cbar_size="10%", label_mode="1")
|
||||
# 4-tuple rect => Divider, isinstance will give True for SubplotDivider
|
||||
assert type(grid.get_divider()) is Divider
|
||||
for i in range(3):
|
||||
im = grid[i].imshow(imdata, interpolation='none')
|
||||
grid.cbar_axes[0].colorbar(im)
|
||||
|
||||
|
||||
def test_image_grid_label_mode_invalid():
|
||||
fig = plt.figure()
|
||||
with pytest.raises(ValueError, match="'foo' is not a valid value for mode"):
|
||||
ImageGrid(fig, (0, 0, 1, 1), (2, 1), label_mode="foo")
|
||||
|
||||
|
||||
@image_comparison(['image_grid.png'],
|
||||
remove_text=True, style='mpl20',
|
||||
savefig_kwarg={'bbox_inches': 'tight'})
|
||||
def test_image_grid():
|
||||
# test that image grid works with bbox_inches=tight.
|
||||
im = np.arange(100).reshape((10, 10))
|
||||
|
||||
fig = plt.figure(1, (4, 4))
|
||||
grid = ImageGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.1)
|
||||
assert grid.get_axes_pad() == (0.1, 0.1)
|
||||
for i in range(4):
|
||||
grid[i].imshow(im, interpolation='nearest')
|
||||
|
||||
|
||||
def test_gettightbbox():
|
||||
fig, ax = plt.subplots(figsize=(8, 6))
|
||||
|
||||
l, = ax.plot([1, 2, 3], [0, 1, 0])
|
||||
|
||||
ax_zoom = zoomed_inset_axes(ax, 4)
|
||||
ax_zoom.plot([1, 2, 3], [0, 1, 0])
|
||||
|
||||
mark_inset(ax, ax_zoom, loc1=1, loc2=3, fc="none", ec='0.3')
|
||||
|
||||
remove_ticks_and_titles(fig)
|
||||
bbox = fig.get_tightbbox(fig.canvas.get_renderer())
|
||||
np.testing.assert_array_almost_equal(bbox.extents,
|
||||
[-17.7, -13.9, 7.2, 5.4])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("click_on", ["big", "small"])
|
||||
@pytest.mark.parametrize("big_on_axes,small_on_axes", [
|
||||
("gca", "gca"),
|
||||
("host", "host"),
|
||||
("host", "parasite"),
|
||||
("parasite", "host"),
|
||||
("parasite", "parasite")
|
||||
])
|
||||
def test_picking_callbacks_overlap(big_on_axes, small_on_axes, click_on):
|
||||
"""Test pick events on normal, host or parasite axes."""
|
||||
# Two rectangles are drawn and "clicked on", a small one and a big one
|
||||
# enclosing the small one. The axis on which they are drawn as well as the
|
||||
# rectangle that is clicked on are varied.
|
||||
# In each case we expect that both rectangles are picked if we click on the
|
||||
# small one and only the big one is picked if we click on the big one.
|
||||
# Also tests picking on normal axes ("gca") as a control.
|
||||
big = plt.Rectangle((0.25, 0.25), 0.5, 0.5, picker=5)
|
||||
small = plt.Rectangle((0.4, 0.4), 0.2, 0.2, facecolor="r", picker=5)
|
||||
# Machinery for "receiving" events
|
||||
received_events = []
|
||||
def on_pick(event):
|
||||
received_events.append(event)
|
||||
plt.gcf().canvas.mpl_connect('pick_event', on_pick)
|
||||
# Shortcut
|
||||
rectangles_on_axes = (big_on_axes, small_on_axes)
|
||||
# Axes setup
|
||||
axes = {"gca": None, "host": None, "parasite": None}
|
||||
if "gca" in rectangles_on_axes:
|
||||
axes["gca"] = plt.gca()
|
||||
if "host" in rectangles_on_axes or "parasite" in rectangles_on_axes:
|
||||
axes["host"] = host_subplot(111)
|
||||
axes["parasite"] = axes["host"].twin()
|
||||
# Add rectangles to axes
|
||||
axes[big_on_axes].add_patch(big)
|
||||
axes[small_on_axes].add_patch(small)
|
||||
# Simulate picking with click mouse event
|
||||
if click_on == "big":
|
||||
click_axes = axes[big_on_axes]
|
||||
axes_coords = (0.3, 0.3)
|
||||
else:
|
||||
click_axes = axes[small_on_axes]
|
||||
axes_coords = (0.5, 0.5)
|
||||
# In reality mouse events never happen on parasite axes, only host axes
|
||||
if click_axes is axes["parasite"]:
|
||||
click_axes = axes["host"]
|
||||
(x, y) = click_axes.transAxes.transform(axes_coords)
|
||||
m = MouseEvent("button_press_event", click_axes.get_figure(root=True).canvas, x, y,
|
||||
button=1)
|
||||
click_axes.pick(m)
|
||||
# Checks
|
||||
expected_n_events = 2 if click_on == "small" else 1
|
||||
assert len(received_events) == expected_n_events
|
||||
event_rects = [event.artist for event in received_events]
|
||||
assert big in event_rects
|
||||
if click_on == "small":
|
||||
assert small in event_rects
|
||||
|
||||
|
||||
@image_comparison(['anchored_artists.png'], remove_text=True, style='mpl20')
|
||||
def test_anchored_artists():
|
||||
fig, ax = plt.subplots(figsize=(3, 3))
|
||||
ada = AnchoredDrawingArea(40, 20, 0, 0, loc='upper right', pad=0.,
|
||||
frameon=False)
|
||||
p1 = Circle((10, 10), 10)
|
||||
ada.drawing_area.add_artist(p1)
|
||||
p2 = Circle((30, 10), 5, fc="r")
|
||||
ada.drawing_area.add_artist(p2)
|
||||
ax.add_artist(ada)
|
||||
|
||||
box = AnchoredAuxTransformBox(ax.transData, loc='upper left')
|
||||
el = Ellipse((0, 0), width=0.1, height=0.4, angle=30, color='cyan')
|
||||
box.drawing_area.add_artist(el)
|
||||
ax.add_artist(box)
|
||||
|
||||
# This block used to test the AnchoredEllipse class, but that was removed. The block
|
||||
# remains, though it duplicates the above ellipse, so that the test image doesn't
|
||||
# need to be regenerated.
|
||||
box = AnchoredAuxTransformBox(ax.transData, loc='lower left', frameon=True,
|
||||
pad=0.5, borderpad=0.4)
|
||||
el = Ellipse((0, 0), width=0.1, height=0.25, angle=-60)
|
||||
box.drawing_area.add_artist(el)
|
||||
ax.add_artist(box)
|
||||
|
||||
asb = AnchoredSizeBar(ax.transData, 0.2, r"0.2 units", loc='lower right',
|
||||
pad=0.3, borderpad=0.4, sep=4, fill_bar=True,
|
||||
frameon=False, label_top=True, prop={'size': 20},
|
||||
size_vertical=0.05, color='green')
|
||||
ax.add_artist(asb)
|
||||
|
||||
|
||||
def test_hbox_divider():
|
||||
arr1 = np.arange(20).reshape((4, 5))
|
||||
arr2 = np.arange(20).reshape((5, 4))
|
||||
|
||||
fig, (ax1, ax2) = plt.subplots(1, 2)
|
||||
ax1.imshow(arr1)
|
||||
ax2.imshow(arr2)
|
||||
|
||||
pad = 0.5 # inches.
|
||||
divider = HBoxDivider(
|
||||
fig, 111, # Position of combined axes.
|
||||
horizontal=[Size.AxesX(ax1), Size.Fixed(pad), Size.AxesX(ax2)],
|
||||
vertical=[Size.AxesY(ax1), Size.Scaled(1), Size.AxesY(ax2)])
|
||||
ax1.set_axes_locator(divider.new_locator(0))
|
||||
ax2.set_axes_locator(divider.new_locator(2))
|
||||
|
||||
fig.canvas.draw()
|
||||
p1 = ax1.get_position()
|
||||
p2 = ax2.get_position()
|
||||
assert p1.height == p2.height
|
||||
assert p2.width / p1.width == pytest.approx((4 / 5) ** 2)
|
||||
|
||||
|
||||
def test_vbox_divider():
|
||||
arr1 = np.arange(20).reshape((4, 5))
|
||||
arr2 = np.arange(20).reshape((5, 4))
|
||||
|
||||
fig, (ax1, ax2) = plt.subplots(1, 2)
|
||||
ax1.imshow(arr1)
|
||||
ax2.imshow(arr2)
|
||||
|
||||
pad = 0.5 # inches.
|
||||
divider = VBoxDivider(
|
||||
fig, 111, # Position of combined axes.
|
||||
horizontal=[Size.AxesX(ax1), Size.Scaled(1), Size.AxesX(ax2)],
|
||||
vertical=[Size.AxesY(ax1), Size.Fixed(pad), Size.AxesY(ax2)])
|
||||
ax1.set_axes_locator(divider.new_locator(0))
|
||||
ax2.set_axes_locator(divider.new_locator(2))
|
||||
|
||||
fig.canvas.draw()
|
||||
p1 = ax1.get_position()
|
||||
p2 = ax2.get_position()
|
||||
assert p1.width == p2.width
|
||||
assert p1.height / p2.height == pytest.approx((4 / 5) ** 2)
|
||||
|
||||
|
||||
def test_axes_class_tuple():
|
||||
fig = plt.figure()
|
||||
axes_class = (mpl_toolkits.axes_grid1.mpl_axes.Axes, {})
|
||||
gr = AxesGrid(fig, 111, nrows_ncols=(1, 1), axes_class=axes_class)
|
||||
|
||||
|
||||
def test_grid_axes_lists():
|
||||
"""Test Grid axes_all, axes_row and axes_column relationship."""
|
||||
fig = plt.figure()
|
||||
grid = Grid(fig, 111, (2, 3), direction="row")
|
||||
assert_array_equal(grid, grid.axes_all)
|
||||
assert_array_equal(grid.axes_row, np.transpose(grid.axes_column))
|
||||
assert_array_equal(grid, np.ravel(grid.axes_row), "row")
|
||||
assert grid.get_geometry() == (2, 3)
|
||||
grid = Grid(fig, 111, (2, 3), direction="column")
|
||||
assert_array_equal(grid, np.ravel(grid.axes_column), "column")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('direction', ('row', 'column'))
|
||||
def test_grid_axes_position(direction):
|
||||
"""Test positioning of the axes in Grid."""
|
||||
fig = plt.figure()
|
||||
grid = Grid(fig, 111, (2, 2), direction=direction)
|
||||
loc = [ax.get_axes_locator() for ax in np.ravel(grid.axes_row)]
|
||||
# Test nx.
|
||||
assert loc[1].args[0] > loc[0].args[0]
|
||||
assert loc[0].args[0] == loc[2].args[0]
|
||||
assert loc[3].args[0] == loc[1].args[0]
|
||||
# Test ny.
|
||||
assert loc[2].args[1] < loc[0].args[1]
|
||||
assert loc[0].args[1] == loc[1].args[1]
|
||||
assert loc[3].args[1] == loc[2].args[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('rect, ngrids, error, message', (
|
||||
((1, 1), None, TypeError, "Incorrect rect format"),
|
||||
(111, -1, ValueError, "ngrids must be positive"),
|
||||
(111, 7, ValueError, "ngrids must be positive"),
|
||||
))
|
||||
def test_grid_errors(rect, ngrids, error, message):
|
||||
fig = plt.figure()
|
||||
with pytest.raises(error, match=message):
|
||||
Grid(fig, rect, (2, 3), ngrids=ngrids)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('anchor, error, message', (
|
||||
(None, TypeError, "anchor must be str"),
|
||||
("CC", ValueError, "'CC' is not a valid value for anchor"),
|
||||
((1, 1, 1), TypeError, "anchor must be str"),
|
||||
))
|
||||
def test_divider_errors(anchor, error, message):
|
||||
fig = plt.figure()
|
||||
with pytest.raises(error, match=message):
|
||||
Divider(fig, [0, 0, 1, 1], [Size.Fixed(1)], [Size.Fixed(1)],
|
||||
anchor=anchor)
|
||||
|
||||
|
||||
@check_figures_equal(extensions=["png"])
|
||||
def test_mark_inset_unstales_viewlim(fig_test, fig_ref):
|
||||
inset, full = fig_test.subplots(1, 2)
|
||||
full.plot([0, 5], [0, 5])
|
||||
inset.set(xlim=(1, 2), ylim=(1, 2))
|
||||
# Check that mark_inset unstales full's viewLim before drawing the marks.
|
||||
mark_inset(full, inset, 1, 4)
|
||||
|
||||
inset, full = fig_ref.subplots(1, 2)
|
||||
full.plot([0, 5], [0, 5])
|
||||
inset.set(xlim=(1, 2), ylim=(1, 2))
|
||||
mark_inset(full, inset, 1, 4)
|
||||
# Manually unstale the full's viewLim.
|
||||
fig_ref.canvas.draw()
|
||||
|
||||
|
||||
def test_auto_adjustable():
|
||||
fig = plt.figure()
|
||||
ax = fig.add_axes([0, 0, 1, 1])
|
||||
pad = 0.1
|
||||
make_axes_area_auto_adjustable(ax, pad=pad)
|
||||
fig.canvas.draw()
|
||||
tbb = ax.get_tightbbox()
|
||||
assert tbb.x0 == pytest.approx(pad * fig.dpi)
|
||||
assert tbb.x1 == pytest.approx(fig.bbox.width - pad * fig.dpi)
|
||||
assert tbb.y0 == pytest.approx(pad * fig.dpi)
|
||||
assert tbb.y1 == pytest.approx(fig.bbox.height - pad * fig.dpi)
|
||||
|
||||
|
||||
# Update style when regenerating the test image
|
||||
@image_comparison(['rgb_axes.png'], remove_text=True,
|
||||
style=('classic', '_classic_test_patch'))
|
||||
def test_rgb_axes():
|
||||
fig = plt.figure()
|
||||
ax = RGBAxes(fig, (0.1, 0.1, 0.8, 0.8), pad=0.1)
|
||||
rng = np.random.default_rng(19680801)
|
||||
r = rng.random((5, 5))
|
||||
g = rng.random((5, 5))
|
||||
b = rng.random((5, 5))
|
||||
ax.imshow_rgb(r, g, b, interpolation='none')
|
||||
|
||||
|
||||
# The original version of this test relied on mpl_toolkits's slightly different
|
||||
# colorbar implementation; moving to matplotlib's own colorbar implementation
|
||||
# caused the small image comparison error.
|
||||
@image_comparison(['imagegrid_cbar_mode.png'],
|
||||
remove_text=True, style='mpl20', tol=0.3)
|
||||
def test_imagegrid_cbar_mode_edge():
|
||||
arr = np.arange(16).reshape((4, 4))
|
||||
|
||||
fig = plt.figure(figsize=(18, 9))
|
||||
|
||||
positions = (241, 242, 243, 244, 245, 246, 247, 248)
|
||||
directions = ['row']*4 + ['column']*4
|
||||
cbar_locations = ['left', 'right', 'top', 'bottom']*2
|
||||
|
||||
for position, direction, location in zip(
|
||||
positions, directions, cbar_locations):
|
||||
grid = ImageGrid(fig, position,
|
||||
nrows_ncols=(2, 2),
|
||||
direction=direction,
|
||||
cbar_location=location,
|
||||
cbar_size='20%',
|
||||
cbar_mode='edge')
|
||||
ax1, ax2, ax3, ax4 = grid
|
||||
|
||||
ax1.imshow(arr, cmap='nipy_spectral')
|
||||
ax2.imshow(arr.T, cmap='hot')
|
||||
ax3.imshow(np.hypot(arr, arr.T), cmap='jet')
|
||||
ax4.imshow(np.arctan2(arr, arr.T), cmap='hsv')
|
||||
|
||||
# In each row/column, the "first" colorbars must be overwritten by the
|
||||
# "second" ones. To achieve this, clear out the axes first.
|
||||
for ax in grid:
|
||||
ax.cax.cla()
|
||||
cb = ax.cax.colorbar(ax.images[0])
|
||||
|
||||
|
||||
def test_imagegrid():
|
||||
fig = plt.figure()
|
||||
grid = ImageGrid(fig, 111, nrows_ncols=(1, 1))
|
||||
ax = grid[0]
|
||||
im = ax.imshow([[1, 2]], norm=mpl.colors.LogNorm())
|
||||
cb = ax.cax.colorbar(im)
|
||||
assert isinstance(cb.locator, mticker.LogLocator)
|
||||
|
||||
|
||||
def test_removal():
|
||||
import matplotlib.pyplot as plt
|
||||
import mpl_toolkits.axisartist as AA
|
||||
fig = plt.figure()
|
||||
ax = host_subplot(111, axes_class=AA.Axes, figure=fig)
|
||||
col = ax.fill_between(range(5), 0, range(5))
|
||||
fig.canvas.draw()
|
||||
col.remove()
|
||||
fig.canvas.draw()
|
||||
|
||||
|
||||
@image_comparison(['anchored_locator_base_call.png'], style="mpl20")
|
||||
def test_anchored_locator_base_call():
|
||||
fig = plt.figure(figsize=(3, 3))
|
||||
fig1, fig2 = fig.subfigures(nrows=2, ncols=1)
|
||||
|
||||
ax = fig1.subplots()
|
||||
ax.set(aspect=1, xlim=(-15, 15), ylim=(-20, 5))
|
||||
ax.set(xticks=[], yticks=[])
|
||||
|
||||
Z = cbook.get_sample_data("axes_grid/bivariate_normal.npy")
|
||||
extent = (-3, 4, -4, 3)
|
||||
|
||||
axins = zoomed_inset_axes(ax, zoom=2, loc="upper left")
|
||||
axins.set(xticks=[], yticks=[])
|
||||
|
||||
axins.imshow(Z, extent=extent, origin="lower")
|
||||
|
||||
|
||||
def test_grid_with_axes_class_not_overriding_axis():
|
||||
Grid(plt.figure(), 111, (2, 2), axes_class=mpl.axes.Axes)
|
||||
RGBAxes(plt.figure(), 111, axes_class=mpl.axes.Axes)
|
||||
Reference in New Issue
Block a user