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