@@ -0,0 +1,5 @@
|
||||
from . import axes_size as Size
|
||||
from .axes_divider import Divider, SubplotDivider, make_axes_locatable
|
||||
from .axes_grid import Grid, ImageGrid, AxesGrid
|
||||
|
||||
from .parasite_axes import host_subplot, host_axes
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,554 @@
|
||||
from matplotlib import 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 : int
|
||||
Location of this artist. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
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 artists.
|
||||
|
||||
**kwargs
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.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 : int
|
||||
Location of this artist. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
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 artists.
|
||||
|
||||
**kwargs
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
drawing_area : `matplotlib.offsetbox.AuxTransformBox`
|
||||
A container for artists to display.
|
||||
|
||||
Examples
|
||||
--------
|
||||
To display an ellipse in the upper left, with a width of 0.1 and
|
||||
height of 0.4 in data coordinates:
|
||||
|
||||
>>> box = AnchoredAuxTransformBox(ax.transData, loc='upper left')
|
||||
>>> el = Ellipse((0, 0), width=0.1, height=0.4, angle=30)
|
||||
>>> box.drawing_area.add_artist(el)
|
||||
>>> ax.add_artist(box)
|
||||
"""
|
||||
self.drawing_area = AuxTransformBox(transform)
|
||||
|
||||
super().__init__(loc, pad=pad, borderpad=borderpad,
|
||||
child=self.drawing_area, prop=prop, frameon=frameon,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class 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 : int
|
||||
Location of this size bar. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
pad : float, optional
|
||||
Padding around the ellipse, in fraction of the font size. Defaults
|
||||
to 0.1.
|
||||
|
||||
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
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
ellipse : `matplotlib.patches.Ellipse`
|
||||
Ellipse patch drawn.
|
||||
"""
|
||||
self._box = AuxTransformBox(transform)
|
||||
self.ellipse = Ellipse((0, 0), width, height, 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 : int
|
||||
Location of this size bar. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
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
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.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 keyworded argument, but *fontproperties* is
|
||||
not, then *prop* is be 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=2, 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 : int, default: 2
|
||||
Location of the direction arrows. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
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.TextPath` and `.patches.FancyArrowPatch`.
|
||||
|
||||
**kwargs
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
arrow_x, arrow_y : `matplotlib.patches.FancyArrowPatch`
|
||||
Arrow x and y
|
||||
|
||||
text_path_x, text_path_y : `matplotlib.textpath.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 be 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,762 @@
|
||||
"""
|
||||
Helper classes to adjust the positions of multiple axes at drawing time.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _api
|
||||
from matplotlib.axes import SubplotBase
|
||||
from matplotlib.gridspec import SubplotSpec, GridSpec
|
||||
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
|
||||
Whether overall rectangular area is reduced so that the relative
|
||||
part of the horizontal and vertical scales have the same scale.
|
||||
anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'}
|
||||
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._aspect = aspect
|
||||
self._xrefindex = 0
|
||||
self._yrefindex = 0
|
||||
self._locator = None
|
||||
|
||||
def get_horizontal_sizes(self, renderer):
|
||||
return [s.get_size(renderer) for s in self.get_horizontal()]
|
||||
|
||||
def get_vertical_sizes(self, renderer):
|
||||
return [s.get_size(renderer) for s in self.get_vertical()]
|
||||
|
||||
def get_vsize_hsize(self):
|
||||
vsize = Size.AddList(self.get_vertical())
|
||||
hsize = Size.AddList(self.get_horizontal())
|
||||
return vsize, hsize
|
||||
|
||||
@staticmethod
|
||||
def _calc_k(l, total_size):
|
||||
|
||||
rs_sum, as_sum = 0., 0.
|
||||
|
||||
for _rs, _as in l:
|
||||
rs_sum += _rs
|
||||
as_sum += _as
|
||||
|
||||
if rs_sum != 0.:
|
||||
k = (total_size - as_sum) / rs_sum
|
||||
return k
|
||||
else:
|
||||
return 0.
|
||||
|
||||
@staticmethod
|
||||
def _calc_offsets(l, k):
|
||||
offsets = [0.]
|
||||
for _rs, _as in l:
|
||||
offsets.append(offsets[-1] + _rs*k + _as)
|
||||
return offsets
|
||||
|
||||
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 : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'}
|
||||
anchor position
|
||||
|
||||
===== ============
|
||||
value description
|
||||
===== ============
|
||||
'C' Center
|
||||
'SW' bottom left
|
||||
'S' bottom
|
||||
'SE' bottom right
|
||||
'E' right
|
||||
'NE' top right
|
||||
'N' top
|
||||
'NW' top left
|
||||
'W' left
|
||||
===== ============
|
||||
|
||||
"""
|
||||
if len(anchor) != 2:
|
||||
_api.check_in_list(mtransforms.Bbox.coefs, anchor=anchor)
|
||||
self._anchor = anchor
|
||||
|
||||
def get_anchor(self):
|
||||
"""Return the anchor."""
|
||||
return self._anchor
|
||||
|
||||
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
|
||||
|
||||
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
|
||||
"""
|
||||
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.
|
||||
axes
|
||||
renderer
|
||||
"""
|
||||
|
||||
figW, figH = self._fig.get_size_inches()
|
||||
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, figW*w)
|
||||
k_v = self._calc_k(vsizes, figH*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]) / figW
|
||||
hh = (oy[-1] - oy[0]) / figH
|
||||
pb = mtransforms.Bbox.from_bounds(x, y, w, h)
|
||||
pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
|
||||
pb1_anchored = pb1.anchored(self.get_anchor(), pb)
|
||||
x0, y0 = pb1_anchored.x0, pb1_anchored.y0
|
||||
|
||||
else:
|
||||
ox = self._calc_offsets(hsizes, k_h)
|
||||
oy = self._calc_offsets(vsizes, k_v)
|
||||
x0, y0 = x, y
|
||||
|
||||
if nx1 is None:
|
||||
nx1 = nx + 1
|
||||
if ny1 is None:
|
||||
ny1 = ny + 1
|
||||
|
||||
x1, w1 = x0 + ox[nx] / figW, (ox[nx1] - ox[nx]) / figW
|
||||
y1, h1 = y0 + oy[ny] / figH, (oy[ny1] - oy[ny]) / figH
|
||||
|
||||
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
|
||||
|
||||
def new_locator(self, nx, ny, nx1=None, ny1=None):
|
||||
"""
|
||||
Return a new `AxesLocator` 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.
|
||||
"""
|
||||
return AxesLocator(self, nx, ny, nx1, ny1)
|
||||
|
||||
def append_size(self, position, size):
|
||||
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
|
||||
elif position == "top":
|
||||
self._vertical.append(size)
|
||||
else:
|
||||
_api.check_in_list(["left", "right", "bottom", "top"],
|
||||
position=position)
|
||||
|
||||
def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None):
|
||||
if adjust_dirs is None:
|
||||
adjust_dirs = ["left", "right", "bottom", "top"]
|
||||
from .axes_size import Padded, SizeFromFunc, GetExtentHelper
|
||||
for d in adjust_dirs:
|
||||
helper = GetExtentHelper(use_axes, d)
|
||||
size = SizeFromFunc(helper)
|
||||
padded_size = Padded(size, pad) # pad in inch
|
||||
self.append_size(d, padded_size)
|
||||
|
||||
|
||||
class AxesLocator:
|
||||
"""
|
||||
A simple callable object, initialized with AxesDivider class,
|
||||
returns the position and size of the given cell.
|
||||
"""
|
||||
def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
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 = nx + 1
|
||||
if ny1 is None:
|
||||
ny1 = ny + 1
|
||||
|
||||
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):
|
||||
if hasattr(self._axes_divider, "get_subplotspec"):
|
||||
return self._axes_divider.get_subplotspec()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
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)).
|
||||
"""
|
||||
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
|
||||
|
||||
@_api.deprecated("3.4")
|
||||
@property
|
||||
def figbox(self):
|
||||
return self.get_subplotspec().get_position(self.figure)
|
||||
|
||||
@_api.deprecated("3.4")
|
||||
def update_params(self):
|
||||
pass
|
||||
|
||||
@_api.deprecated(
|
||||
"3.4", alternative="get_subplotspec",
|
||||
addendum="(get_subplotspec returns a SubplotSpec instance.)")
|
||||
def get_geometry(self):
|
||||
"""Get the subplot geometry, e.g., (2, 2, 3)."""
|
||||
rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
|
||||
return rows, cols, num1 + 1 # for compatibility
|
||||
|
||||
@_api.deprecated("3.4", alternative="set_subplotspec")
|
||||
def change_geometry(self, numrows, numcols, num):
|
||||
"""Change subplot geometry, e.g., from (1, 1, 1) to (2, 2, 3)."""
|
||||
self._subplotspec = GridSpec(numrows, numcols)[num-1]
|
||||
self.update_params()
|
||||
self.set_position(self.figbox)
|
||||
|
||||
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 pre-existing 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:
|
||||
if isinstance(axes, SubplotBase):
|
||||
axes_class = axes._axes_class
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
Add a new axes on the right (or left) side of the main axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
size : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
|
||||
A width of the axes. If float or string is given, *from_any*
|
||||
function is used to create the size, with *ref_size* set to AxesX
|
||||
instance of the current axes.
|
||||
pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
|
||||
Pad between the axes. It takes same argument as *size*.
|
||||
pack_start : bool
|
||||
If False, the new axes is appended at the end
|
||||
of the list, i.e., it became the right-most axes. If True, it is
|
||||
inserted at the start of the list, and becomes the left-most axes.
|
||||
**kwargs
|
||||
All extra keywords arguments are passed to the created axes.
|
||||
If *axes_class* is given, the new axes will be created as an
|
||||
instance of the given class. Otherwise, the same class of the
|
||||
main axes will be used.
|
||||
"""
|
||||
if pad is None:
|
||||
_api.warn_deprecated(
|
||||
"3.2", message="In a future version, 'pad' will default to "
|
||||
"rcParams['figure.subplot.wspace']. Set pad=0 to keep the "
|
||||
"old behavior.")
|
||||
if pad:
|
||||
if not isinstance(pad, Size._Base):
|
||||
pad = Size.from_any(pad, fraction_ref=self._xref)
|
||||
if pack_start:
|
||||
self._horizontal.insert(0, pad)
|
||||
self._xrefindex += 1
|
||||
else:
|
||||
self._horizontal.append(pad)
|
||||
if not isinstance(size, Size._Base):
|
||||
size = Size.from_any(size, fraction_ref=self._xref)
|
||||
if pack_start:
|
||||
self._horizontal.insert(0, size)
|
||||
self._xrefindex += 1
|
||||
locator = self.new_locator(nx=0, ny=self._yrefindex)
|
||||
else:
|
||||
self._horizontal.append(size)
|
||||
locator = self.new_locator(
|
||||
nx=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):
|
||||
"""
|
||||
Add a new axes on the top (or bottom) side of the main axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
size : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
|
||||
A height of the axes. If float or string is given, *from_any*
|
||||
function is used to create the size, with *ref_size* set to AxesX
|
||||
instance of the current axes.
|
||||
pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
|
||||
Pad between the axes. It takes same argument as *size*.
|
||||
pack_start : bool
|
||||
If False, the new axes is appended at the end
|
||||
of the list, i.e., it became the right-most axes. If True, it is
|
||||
inserted at the start of the list, and becomes the left-most axes.
|
||||
**kwargs
|
||||
All extra keywords arguments are passed to the created axes.
|
||||
If *axes_class* is given, the new axes will be created as an
|
||||
instance of the given class. Otherwise, the same class of the
|
||||
main axes will be used.
|
||||
"""
|
||||
if pad is None:
|
||||
_api.warn_deprecated(
|
||||
"3.2", message="In a future version, 'pad' will default to "
|
||||
"rcParams['figure.subplot.hspace']. Set pad=0 to keep the "
|
||||
"old behavior.")
|
||||
if pad:
|
||||
if not isinstance(pad, Size._Base):
|
||||
pad = Size.from_any(pad, fraction_ref=self._yref)
|
||||
if pack_start:
|
||||
self._vertical.insert(0, pad)
|
||||
self._yrefindex += 1
|
||||
else:
|
||||
self._vertical.append(pad)
|
||||
if not isinstance(size, Size._Base):
|
||||
size = Size.from_any(size, fraction_ref=self._yref)
|
||||
if pack_start:
|
||||
self._vertical.insert(0, size)
|
||||
self._yrefindex += 1
|
||||
locator = self.new_locator(nx=self._xrefindex, ny=0)
|
||||
else:
|
||||
self._vertical.append(size)
|
||||
locator = self.new_locator(
|
||||
nx=self._xrefindex, ny=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, add_to_figure=True,
|
||||
**kwargs):
|
||||
"""
|
||||
Create an axes at the given *position* with the same height
|
||||
(or width) of the main axes.
|
||||
|
||||
*position*
|
||||
["left"|"right"|"bottom"|"top"]
|
||||
|
||||
*size* and *pad* should be axes_grid.axes_size compatible.
|
||||
"""
|
||||
if position == "left":
|
||||
ax = self.new_horizontal(size, pad, pack_start=True, **kwargs)
|
||||
elif position == "right":
|
||||
ax = self.new_horizontal(size, pad, pack_start=False, **kwargs)
|
||||
elif position == "bottom":
|
||||
ax = self.new_vertical(size, pad, pack_start=True, **kwargs)
|
||||
elif position == "top":
|
||||
ax = self.new_vertical(size, pad, pack_start=False, **kwargs)
|
||||
else:
|
||||
_api.check_in_list(["left", "right", "bottom", "top"],
|
||||
position=position)
|
||||
if add_to_figure:
|
||||
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):
|
||||
if hasattr(self._axes, "get_subplotspec"):
|
||||
return self._axes.get_subplotspec()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class HBoxDivider(SubplotDivider):
|
||||
|
||||
@staticmethod
|
||||
def _determine_karray(equivalent_sizes, appended_sizes,
|
||||
max_equivalent_size,
|
||||
total_appended_size):
|
||||
|
||||
n = len(equivalent_sizes)
|
||||
eq_rs, eq_as = np.asarray(equivalent_sizes).T
|
||||
ap_rs, ap_as = np.asarray(appended_sizes).T
|
||||
A = np.zeros((n + 1, n + 1))
|
||||
B = np.zeros(n + 1)
|
||||
np.fill_diagonal(A[:n, :n], eq_rs)
|
||||
A[:n, -1] = -1
|
||||
A[-1, :-1] = ap_rs
|
||||
B[:n] = -eq_as
|
||||
B[-1] = total_appended_size - sum(ap_as)
|
||||
|
||||
karray_H = np.linalg.solve(A, B) # A @ K = B
|
||||
karray = karray_H[:-1]
|
||||
H = karray_H[-1]
|
||||
|
||||
if H > max_equivalent_size:
|
||||
karray = (max_equivalent_size - eq_as) / eq_rs
|
||||
return karray
|
||||
|
||||
@staticmethod
|
||||
def _calc_offsets(appended_sizes, karray):
|
||||
offsets = [0.]
|
||||
for (r, a), k in zip(appended_sizes, karray):
|
||||
offsets.append(offsets[-1] + r*k + a)
|
||||
return offsets
|
||||
|
||||
def new_locator(self, nx, nx1=None):
|
||||
"""
|
||||
Create a new `AxesLocator` 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.
|
||||
"""
|
||||
return AxesLocator(self, nx, 0, nx1, None)
|
||||
|
||||
def _locate(self, x, y, w, h,
|
||||
y_equivalent_sizes, x_appended_sizes,
|
||||
figW, figH):
|
||||
equivalent_sizes = y_equivalent_sizes
|
||||
appended_sizes = x_appended_sizes
|
||||
|
||||
max_equivalent_size = figH * h
|
||||
total_appended_size = figW * w
|
||||
karray = self._determine_karray(equivalent_sizes, appended_sizes,
|
||||
max_equivalent_size,
|
||||
total_appended_size)
|
||||
|
||||
ox = self._calc_offsets(appended_sizes, karray)
|
||||
|
||||
ww = (ox[-1] - ox[0]) / figW
|
||||
ref_h = equivalent_sizes[0]
|
||||
hh = (karray[0]*ref_h[0] + ref_h[1]) / figH
|
||||
pb = mtransforms.Bbox.from_bounds(x, y, w, h)
|
||||
pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
|
||||
pb1_anchored = pb1.anchored(self.get_anchor(), pb)
|
||||
x0, y0 = pb1_anchored.x0, pb1_anchored.y0
|
||||
|
||||
return x0, y0, ox, hh
|
||||
|
||||
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
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.
|
||||
axes
|
||||
renderer
|
||||
"""
|
||||
|
||||
figW, figH = self._fig.get_size_inches()
|
||||
x, y, w, h = self.get_position_runtime(axes, renderer)
|
||||
|
||||
y_equivalent_sizes = self.get_vertical_sizes(renderer)
|
||||
x_appended_sizes = self.get_horizontal_sizes(renderer)
|
||||
x0, y0, ox, hh = self._locate(x, y, w, h,
|
||||
y_equivalent_sizes, x_appended_sizes,
|
||||
figW, figH)
|
||||
if nx1 is None:
|
||||
nx1 = nx + 1
|
||||
|
||||
x1, w1 = x0 + ox[nx] / figW, (ox[nx1] - ox[nx]) / figW
|
||||
y1, h1 = y0, hh
|
||||
|
||||
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
|
||||
|
||||
|
||||
class VBoxDivider(HBoxDivider):
|
||||
"""
|
||||
The Divider class whose rectangle area is specified as a subplot geometry.
|
||||
"""
|
||||
|
||||
def new_locator(self, ny, ny1=None):
|
||||
"""
|
||||
Create a new `AxesLocator` 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 AxesLocator(self, 0, ny, None, ny1)
|
||||
|
||||
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
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.
|
||||
axes
|
||||
renderer
|
||||
"""
|
||||
|
||||
figW, figH = self._fig.get_size_inches()
|
||||
x, y, w, h = self.get_position_runtime(axes, renderer)
|
||||
|
||||
x_equivalent_sizes = self.get_horizontal_sizes(renderer)
|
||||
y_appended_sizes = self.get_vertical_sizes(renderer)
|
||||
|
||||
y0, x0, oy, ww = self._locate(y, x, h, w,
|
||||
x_equivalent_sizes, y_appended_sizes,
|
||||
figH, figW)
|
||||
if ny1 is None:
|
||||
ny1 = ny + 1
|
||||
|
||||
x1, w1 = x0, ww
|
||||
y1, h1 = y0 + oy[ny] / figH, (oy[ny1] - oy[ny]) / figH
|
||||
|
||||
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):
|
||||
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,605 @@
|
||||
from numbers import Number
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
|
||||
import matplotlib as mpl
|
||||
from matplotlib import _api
|
||||
from matplotlib.gridspec import SubplotSpec
|
||||
|
||||
from .axes_divider import Size, SubplotDivider, Divider
|
||||
from .mpl_axes import Axes
|
||||
|
||||
|
||||
def _tick_only(ax, bottom_on, left_on):
|
||||
bottom_off = not bottom_on
|
||||
left_off = not left_on
|
||||
ax.axis["bottom"].toggle(ticklabels=bottom_off, label=bottom_off)
|
||||
ax.axis["left"].toggle(ticklabels=left_off, label=left_off)
|
||||
|
||||
|
||||
class CbarAxesBase:
|
||||
def __init__(self, *args, orientation, **kwargs):
|
||||
self.orientation = orientation
|
||||
self._default_label_on = True
|
||||
self._locator = None # deprecated.
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def colorbar(self, mappable, *, ticks=None, **kwargs):
|
||||
|
||||
if self.orientation in ["top", "bottom"]:
|
||||
orientation = "horizontal"
|
||||
else:
|
||||
orientation = "vertical"
|
||||
|
||||
cb = mpl.colorbar.Colorbar(
|
||||
self, mappable, orientation=orientation, ticks=ticks, **kwargs)
|
||||
self._cbid = mappable.colorbar_cid # deprecated in 3.3.
|
||||
self._locator = cb.locator # deprecated in 3.3.
|
||||
|
||||
self._config_axes()
|
||||
return cb
|
||||
|
||||
cbid = _api.deprecate_privatize_attribute(
|
||||
"3.3", alternative="mappable.colorbar_cid")
|
||||
locator = _api.deprecate_privatize_attribute(
|
||||
"3.3", alternative=".colorbar().locator")
|
||||
|
||||
def _config_axes(self):
|
||||
"""Make an axes patch and outline."""
|
||||
ax = self
|
||||
ax.set_navigate(False)
|
||||
ax.axis[:].toggle(all=False)
|
||||
b = self._default_label_on
|
||||
ax.axis[self.orientation].toggle(all=b)
|
||||
|
||||
def toggle_label(self, b):
|
||||
self._default_label_on = b
|
||||
axis = self.axis[self.orientation]
|
||||
axis.toggle(ticklabels=b, label=b)
|
||||
|
||||
def cla(self):
|
||||
super().cla()
|
||||
self._config_axes()
|
||||
|
||||
|
||||
class CbarAxes(CbarAxesBase, Axes):
|
||||
pass
|
||||
|
||||
|
||||
class Grid:
|
||||
"""
|
||||
A grid of Axes.
|
||||
|
||||
In Matplotlib, the axes location (and size) is specified in normalized
|
||||
figure coordinates. This may not be ideal for images that needs to be
|
||||
displayed with a given aspect ratio; for example, it is difficult to
|
||||
display multiple images of a same size with some fixed padding between
|
||||
them. AxesGrid can be used in such case.
|
||||
"""
|
||||
|
||||
_defaultAxesClass = Axes
|
||||
|
||||
@_api.delete_parameter("3.3", "add_all")
|
||||
def __init__(self, fig,
|
||||
rect,
|
||||
nrows_ncols,
|
||||
ngrids=None,
|
||||
direction="row",
|
||||
axes_pad=0.02,
|
||||
add_all=True,
|
||||
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) 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").
|
||||
axes_pad : float or (float, float), default: 0.02
|
||||
Padding or (horizontal padding, vertical padding) between axes, in
|
||||
inches.
|
||||
add_all : bool, default: True
|
||||
Whether to add the axes to the figure using `.Figure.add_axes`.
|
||||
This parameter is deprecated.
|
||||
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"}, 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.
|
||||
|
||||
axes_class : subclass of `matplotlib.axes.Axes`, default: None
|
||||
aspect : bool, default: False
|
||||
Whether the axes aspect ratio follows the aspect ratio of the data
|
||||
limits.
|
||||
"""
|
||||
self._nrows, self._ncols = nrows_ncols
|
||||
|
||||
if ngrids is None:
|
||||
ngrids = self._nrows * self._ncols
|
||||
else:
|
||||
if not 0 < ngrids <= self._nrows * self._ncols:
|
||||
raise Exception("")
|
||||
|
||||
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, (str, 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 Exception("")
|
||||
|
||||
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().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()
|
||||
|
||||
if add_all:
|
||||
for ax in self.axes_all:
|
||||
fig.add_axes(ax)
|
||||
|
||||
self.set_label_mode(label_mode)
|
||||
|
||||
def _init_locators(self):
|
||||
|
||||
h = []
|
||||
h_ax_pos = []
|
||||
for _ in range(self._ncols):
|
||||
if h:
|
||||
h.append(self._horiz_pad_size)
|
||||
h_ax_pos.append(len(h))
|
||||
sz = Size.Scaled(1)
|
||||
h.append(sz)
|
||||
|
||||
v = []
|
||||
v_ax_pos = []
|
||||
for _ in range(self._nrows):
|
||||
if v:
|
||||
v.append(self._vert_pad_size)
|
||||
v_ax_pos.append(len(v))
|
||||
sz = Size.Scaled(1)
|
||||
v.append(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)
|
||||
|
||||
self._divider.set_horizontal(h)
|
||||
self._divider.set_vertical(v)
|
||||
|
||||
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"}
|
||||
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.
|
||||
"""
|
||||
if mode == "all":
|
||||
for ax in self.axes_all:
|
||||
_tick_only(ax, False, False)
|
||||
elif mode == "L":
|
||||
# left-most axes
|
||||
for ax in self.axes_column[0][:-1]:
|
||||
_tick_only(ax, bottom_on=True, left_on=False)
|
||||
# lower-left axes
|
||||
ax = self.axes_column[0][-1]
|
||||
_tick_only(ax, bottom_on=False, left_on=False)
|
||||
|
||||
for col in self.axes_column[1:]:
|
||||
# axes with no labels
|
||||
for ax in col[:-1]:
|
||||
_tick_only(ax, bottom_on=True, left_on=True)
|
||||
|
||||
# bottom
|
||||
ax = col[-1]
|
||||
_tick_only(ax, bottom_on=False, left_on=True)
|
||||
|
||||
elif mode == "1":
|
||||
for ax in self.axes_all:
|
||||
_tick_only(ax, bottom_on=True, left_on=True)
|
||||
|
||||
ax = self.axes_llc
|
||||
_tick_only(ax, bottom_on=False, left_on=False)
|
||||
|
||||
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()
|
||||
|
||||
def get_vsize_hsize(self):
|
||||
return self._divider.get_vsize_hsize()
|
||||
|
||||
|
||||
class ImageGrid(Grid):
|
||||
# docstring inherited
|
||||
|
||||
_defaultCbarAxesClass = CbarAxes
|
||||
|
||||
@_api.delete_parameter("3.3", "add_all")
|
||||
def __init__(self, fig,
|
||||
rect,
|
||||
nrows_ncols,
|
||||
ngrids=None,
|
||||
direction="row",
|
||||
axes_pad=0.02,
|
||||
add_all=True,
|
||||
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.
|
||||
add_all : bool, default: True
|
||||
Whether to add the axes to the figure using `.Figure.add_axes`.
|
||||
This parameter is deprecated.
|
||||
share_all : bool, default: False
|
||||
Whether all axes share their x- and 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
|
||||
"""
|
||||
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().
|
||||
|
||||
if add_all:
|
||||
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)
|
||||
else: # Only show deprecation in that case.
|
||||
super().__init__(
|
||||
fig, rect, nrows_ncols, ngrids,
|
||||
direction=direction, axes_pad=axes_pad, add_all=add_all,
|
||||
share_all=share_all, share_x=True, share_y=True, aspect=aspect,
|
||||
label_mode=label_mode, axes_class=axes_class)
|
||||
|
||||
if add_all:
|
||||
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 = [
|
||||
self._defaultCbarAxesClass(
|
||||
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,168 @@
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import _api
|
||||
from .axes_divider import make_axes_locatable, Size
|
||||
from .mpl_axes import Axes
|
||||
|
||||
|
||||
@_api.delete_parameter("3.3", "add_all")
|
||||
def make_rgb_axes(ax, pad=0.01, axes_class=None, add_all=True, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
pad : float
|
||||
Fraction of the axes height.
|
||||
"""
|
||||
|
||||
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:
|
||||
try:
|
||||
axes_class = ax._axes_class
|
||||
except AttributeError:
|
||||
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)
|
||||
|
||||
if add_all:
|
||||
fig = ax.get_figure()
|
||||
for ax1 in ax_rgb:
|
||||
fig.add_axes(ax1)
|
||||
|
||||
return ax_rgb
|
||||
|
||||
|
||||
@_api.deprecated("3.3", alternative="ax.imshow(np.dstack([r, g, b]))")
|
||||
def imshow_rgb(ax, r, g, b, **kwargs):
|
||||
return ax.imshow(np.dstack([r, g, b]), **kwargs)
|
||||
|
||||
|
||||
class RGBAxes:
|
||||
"""
|
||||
4-panel imshow (RGB, R, G, B).
|
||||
|
||||
Layout:
|
||||
|
||||
+---------------+-----+
|
||||
| | R |
|
||||
+ +-----+
|
||||
| RGB | G |
|
||||
+ +-----+
|
||||
| | B |
|
||||
+---------------+-----+
|
||||
|
||||
Subclasses can override the ``_defaultAxesClass`` attribute.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
RGB : ``_defaultAxesClass``
|
||||
The axes object for the three-channel imshow.
|
||||
R : ``_defaultAxesClass``
|
||||
The axes object for the red channel imshow.
|
||||
G : ``_defaultAxesClass``
|
||||
The axes object for the green channel imshow.
|
||||
B : ``_defaultAxesClass``
|
||||
The axes object for the blue channel imshow.
|
||||
"""
|
||||
|
||||
_defaultAxesClass = Axes
|
||||
|
||||
@_api.delete_parameter("3.3", "add_all")
|
||||
def __init__(self, *args, pad=0, add_all=True, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
pad : float, default: 0
|
||||
fraction of the axes height to put as padding.
|
||||
add_all : bool, default: True
|
||||
Whether to add the {rgb, r, g, b} axes to the figure.
|
||||
This parameter is deprecated.
|
||||
axes_class : matplotlib.axes.Axes
|
||||
|
||||
*args
|
||||
Unpacked into axes_class() init for RGB
|
||||
**kwargs
|
||||
Unpacked into axes_class() init for RGB, R, G, B axes
|
||||
"""
|
||||
axes_class = kwargs.pop("axes_class", self._defaultAxesClass)
|
||||
self.RGB = ax = axes_class(*args, **kwargs)
|
||||
if add_all:
|
||||
ax.get_figure().add_axes(ax)
|
||||
else:
|
||||
kwargs["add_all"] = add_all # only show deprecation in that case
|
||||
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]:
|
||||
ax1.axis[:].line.set_color("w")
|
||||
ax1.axis[:].major_ticks.set_markeredgecolor("w")
|
||||
|
||||
@_api.deprecated("3.3")
|
||||
def add_RGB_to_figure(self):
|
||||
"""Add red, green and blue axes to the RGB composite's axes figure."""
|
||||
self.RGB.get_figure().add_axes(self.R)
|
||||
self.RGB.get_figure().add_axes(self.G)
|
||||
self.RGB.get_figure().add_axes(self.B)
|
||||
|
||||
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 : imshow kwargs
|
||||
kwargs get unpacked into the 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
|
||||
|
||||
|
||||
@_api.deprecated("3.3", alternative="RGBAxes")
|
||||
class RGBAxesBase(RGBAxes):
|
||||
pass
|
@@ -0,0 +1,272 @@
|
||||
"""
|
||||
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 Number
|
||||
|
||||
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))
|
||||
|
||||
|
||||
class Add(_Base):
|
||||
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 AddList(_Base):
|
||||
def __init__(self, add_list):
|
||||
self._list = add_list
|
||||
|
||||
def get_size(self, renderer):
|
||||
sum_rel_size = sum([a.get_size(renderer)[0] for a in self._list])
|
||||
sum_abs_size = sum([a.get_size(renderer)[1] for a in self._list])
|
||||
return sum_rel_size, sum_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(Number, 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(Number, 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
|
||||
|
||||
|
||||
class Padded(_Base):
|
||||
"""
|
||||
Return a instance where the absolute part of *size* is
|
||||
increase by the amount of *pad*.
|
||||
"""
|
||||
|
||||
def __init__(self, size, pad):
|
||||
self._size = size
|
||||
self._pad = pad
|
||||
|
||||
def get_size(self, renderer):
|
||||
r, a = self._size.get_size(renderer)
|
||||
rel_size = r
|
||||
abs_size = a + self._pad
|
||||
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.
|
||||
|
||||
>>> a = Size.from_any(1.2) # => Size.Fixed(1.2)
|
||||
>>> Size.from_any("50%", a) # => Size.Fraction(0.5, a)
|
||||
"""
|
||||
if isinstance(size, Number):
|
||||
return Fixed(size)
|
||||
elif isinstance(size, str):
|
||||
if size[-1] == "%":
|
||||
return Fraction(float(size[:-1]) / 100, fraction_ref)
|
||||
raise ValueError("Unknown format")
|
||||
|
||||
|
||||
class SizeFromFunc(_Base):
|
||||
def __init__(self, func):
|
||||
self._func = func
|
||||
|
||||
def get_size(self, renderer):
|
||||
rel_size = 0.
|
||||
|
||||
bb = self._func(renderer)
|
||||
dpi = renderer.points_to_pixels(72.)
|
||||
abs_size = bb/dpi
|
||||
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class GetExtentHelper:
|
||||
_get_func_map = {
|
||||
"left": lambda self, axes_bbox: axes_bbox.xmin - self.xmin,
|
||||
"right": lambda self, axes_bbox: self.xmax - axes_bbox.xmax,
|
||||
"bottom": lambda self, axes_bbox: axes_bbox.ymin - self.ymin,
|
||||
"top": lambda self, axes_bbox: self.ymax - axes_bbox.ymax,
|
||||
}
|
||||
|
||||
def __init__(self, ax, direction):
|
||||
_api.check_in_list(self._get_func_map, direction=direction)
|
||||
self._ax_list = [ax] if isinstance(ax, Axes) else ax
|
||||
self._direction = direction
|
||||
|
||||
def __call__(self, renderer):
|
||||
get_func = self._get_func_map[self._direction]
|
||||
vl = [get_func(ax.get_tightbbox(renderer, call_axes_locator=False),
|
||||
ax.bbox)
|
||||
for ax in self._ax_list]
|
||||
return max(vl)
|
@@ -0,0 +1,652 @@
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
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's 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(ax, [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):
|
||||
self.axes = ax
|
||||
|
||||
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
|
||||
self._update_offset_func(renderer, fontsize)
|
||||
|
||||
width, height, xdescent, ydescent = self.get_extent(renderer)
|
||||
|
||||
px, py = self.get_offset(width, height, 0, 0, renderer)
|
||||
bbox_canvas = Bbox.from_bounds(px, py, width, height)
|
||||
tr = ax.figure.transFigure.inverted()
|
||||
bb = TransformedBbox(bbox_canvas, tr)
|
||||
|
||||
return bb
|
||||
|
||||
|
||||
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_extent(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
|
||||
|
||||
xd, yd = 0, 0
|
||||
|
||||
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
|
||||
pad = self.pad * fontsize
|
||||
|
||||
return width + 2 * pad, height + 2 * pad, xd + pad, yd + 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_extent(self, renderer):
|
||||
bb = TransformedBbox(self.axes.viewLim, self.parent_axes.transData)
|
||||
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
|
||||
pad = self.pad * fontsize
|
||||
return (abs(bb.width * self.zoom) + 2 * pad,
|
||||
abs(bb.height * self.zoom) + 2 * pad,
|
||||
pad, 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([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)],
|
||||
closed=True)
|
||||
|
||||
|
||||
class BboxConnector(Patch):
|
||||
@staticmethod
|
||||
def get_bbox_edge_pos(bbox, loc):
|
||||
"""
|
||||
Helper function to obtain the location of a corner of a bbox
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bbox : `matplotlib.transforms.Bbox`
|
||||
|
||||
loc : {1, 2, 3, 4}
|
||||
Corner of *bbox*. Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
Returns
|
||||
-------
|
||||
x, y : float
|
||||
Coordinates of the corner specified by *loc*.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Helper function to obtain a Path from one bbox to another.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bbox1, bbox2 : `matplotlib.transforms.Bbox`
|
||||
Bounding boxes to connect.
|
||||
|
||||
loc1 : {1, 2, 3, 4}
|
||||
Corner of *bbox1* to use. Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
loc2 : {1, 2, 3, 4}, optional
|
||||
Corner of *bbox2* to use. If None, defaults to *loc1*.
|
||||
Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
Returns
|
||||
-------
|
||||
path : `matplotlib.path.Path`
|
||||
A line segment from the *loc1* corner of *bbox1* to the *loc2*
|
||||
corner of *bbox2*.
|
||||
"""
|
||||
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 : {1, 2, 3, 4}
|
||||
Corner of *bbox1* to draw the line. Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
loc2 : {1, 2, 3, 4}, optional
|
||||
Corner of *bbox2* to draw the line. If None, defaults to *loc1*.
|
||||
Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
**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()
|
||||
if 'fill' in kwargs:
|
||||
super().__init__(**kwargs)
|
||||
else:
|
||||
fill = bool({'fc', 'facecolor', 'color'}.intersection(kwargs))
|
||||
super().__init__(fill=fill, **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 : {1, 2, 3, 4}
|
||||
Corners of *bbox1* and *bbox2* to draw the first line.
|
||||
Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
loc1b, loc2b : {1, 2, 3, 4}
|
||||
Corners of *bbox1* and *bbox2* to draw the second line.
|
||||
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, inset_axes):
|
||||
"""Helper function to add an inset axes and disable navigation in it"""
|
||||
parent_axes.figure.add_axes(inset_axes)
|
||||
inset_axes.set_navigate(False)
|
||||
|
||||
|
||||
@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=3)
|
||||
|
||||
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 : int or str, default: 1
|
||||
Location to place the inset axes. The valid locations are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
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, optional
|
||||
If specified, the inset axes created will be created with this class's
|
||||
constructor.
|
||||
|
||||
axes_kwargs : dict, optional
|
||||
Keyworded 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 axes_class is None:
|
||||
axes_class = HostAxes
|
||||
|
||||
if axes_kwargs is None:
|
||||
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position())
|
||||
else:
|
||||
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(),
|
||||
**axes_kwargs)
|
||||
|
||||
if bbox_transform in [parent_axes.transAxes,
|
||||
parent_axes.figure.transFigure]:
|
||||
if 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.")
|
||||
|
||||
axes_locator = AnchoredSizeLocator(bbox_to_anchor,
|
||||
width, height,
|
||||
loc=loc,
|
||||
bbox_transform=bbox_transform,
|
||||
borderpad=borderpad)
|
||||
|
||||
inset_axes.set_axes_locator(axes_locator)
|
||||
|
||||
_add_inset_axes(parent_axes, inset_axes)
|
||||
|
||||
return inset_axes
|
||||
|
||||
|
||||
@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 enlargen the
|
||||
coordinates (i.e., "zoomed in"), while *zoom* < 1 will shrink the
|
||||
coordinates (i.e., "zoomed out").
|
||||
|
||||
loc : int or str, default: 'upper right'
|
||||
Location to place the inset axes. The valid locations are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
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, optional
|
||||
If specified, the inset axes created will be created with this class's
|
||||
constructor.
|
||||
|
||||
axes_kwargs : dict, optional
|
||||
Keyworded 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 axes_class is None:
|
||||
axes_class = HostAxes
|
||||
|
||||
if axes_kwargs is None:
|
||||
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position())
|
||||
else:
|
||||
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(),
|
||||
**axes_kwargs)
|
||||
|
||||
axes_locator = AnchoredZoomLocator(parent_axes, zoom=zoom, loc=loc,
|
||||
bbox_to_anchor=bbox_to_anchor,
|
||||
bbox_transform=bbox_transform,
|
||||
borderpad=borderpad)
|
||||
inset_axes.set_axes_locator(axes_locator)
|
||||
|
||||
_add_inset_axes(parent_axes, inset_axes)
|
||||
|
||||
return inset_axes
|
||||
|
||||
|
||||
@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 = TransformedBbox(inset_axes.viewLim, parent_axes.transData)
|
||||
|
||||
if 'fill' in kwargs:
|
||||
pp = BboxPatch(rect, **kwargs)
|
||||
else:
|
||||
fill = bool({'fc', 'facecolor', 'color'}.intersection(kwargs))
|
||||
pp = BboxPatch(rect, fill=fill, **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,134 @@
|
||||
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)
|
||||
|
||||
def _init_axis_artists(self, axes=None):
|
||||
if axes is None:
|
||||
axes = self
|
||||
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"]))
|
||||
|
||||
@property
|
||||
def axis(self):
|
||||
return self._axislines
|
||||
|
||||
def cla(self):
|
||||
super().cla()
|
||||
self._init_axis_artists()
|
||||
|
||||
|
||||
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
|
||||
|
||||
tickOn = "tick%dOn" % self._axisnum
|
||||
labelOn = "label%dOn" % self._axisnum
|
||||
|
||||
if _ticks is not None:
|
||||
tickparam = {tickOn: _ticks}
|
||||
self._axis.set_tick_params(**tickparam)
|
||||
if _ticklabels is not None:
|
||||
tickparam = {labelOn: _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,390 @@
|
||||
import functools
|
||||
|
||||
from matplotlib import _api
|
||||
import matplotlib.artist as martist
|
||||
import matplotlib.transforms as mtransforms
|
||||
from matplotlib.axes import subplot_class_factory
|
||||
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 cla(self):
|
||||
super().cla()
|
||||
martist.setp(self.get_children(), visible=False)
|
||||
self._get_lines = self._parent_axes._get_lines
|
||||
|
||||
def get_images_artists(self):
|
||||
artists = {a for a in self.get_children() if a.get_visible()}
|
||||
images = {a for a in self.images if a.get_visible()}
|
||||
return list(images), list(artists - images)
|
||||
|
||||
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
|
||||
|
||||
@_api.deprecated("3.4", alternative="apply_aspect")
|
||||
def update_viewlim(self):
|
||||
return self._update_viewlim
|
||||
|
||||
def _update_viewlim(self): # Inline after deprecation elapses.
|
||||
viewlim = self._parent_axes.viewLim.frozen()
|
||||
mode = self.get_viewlim_mode()
|
||||
if mode is None:
|
||||
pass
|
||||
elif mode == "equal":
|
||||
self.axes.viewLim.set(viewlim)
|
||||
elif mode == "transform":
|
||||
self.axes.viewLim.set(
|
||||
viewlim.transformed(self.transAux.inverted()))
|
||||
else:
|
||||
_api.check_in_list([None, "equal", "transform"], mode=mode)
|
||||
|
||||
def apply_aspect(self, position=None):
|
||||
self._update_viewlim()
|
||||
super().apply_aspect()
|
||||
|
||||
# end of aux_transform support
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def parasite_axes_class_factory(axes_class=None):
|
||||
if axes_class is None:
|
||||
_api.warn_deprecated(
|
||||
"3.3", message="Support for passing None to "
|
||||
"parasite_axes_class_factory is deprecated since %(since)s and "
|
||||
"will be removed %(removal)s; explicitly pass the default Axes "
|
||||
"class instead.")
|
||||
axes_class = Axes
|
||||
|
||||
return type("%sParasite" % axes_class.__name__,
|
||||
(ParasiteAxesBase, axes_class), {})
|
||||
|
||||
|
||||
ParasiteAxes = parasite_axes_class_factory(Axes)
|
||||
|
||||
|
||||
@_api.deprecated("3.4", alternative="ParasiteAxesBase")
|
||||
class ParasiteAxesAuxTransBase:
|
||||
def __init__(self, parent_axes, aux_transform, viewlim_mode=None,
|
||||
**kwargs):
|
||||
# Explicit wrapper for deprecation to work.
|
||||
super().__init__(parent_axes, aux_transform,
|
||||
viewlim_mode=viewlim_mode, **kwargs)
|
||||
|
||||
def _set_lim_and_transforms(self):
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
@_api.deprecated("3.4", alternative="apply_aspect")
|
||||
def update_viewlim(self):
|
||||
return self._update_viewlim()
|
||||
|
||||
def _update_viewlim(self): # Inline after deprecation elapses.
|
||||
viewlim = self._parent_axes.viewLim.frozen()
|
||||
mode = self.get_viewlim_mode()
|
||||
if mode is None:
|
||||
pass
|
||||
elif mode == "equal":
|
||||
self.axes.viewLim.set(viewlim)
|
||||
elif mode == "transform":
|
||||
self.axes.viewLim.set(
|
||||
viewlim.transformed(self.transAux.inverted()))
|
||||
else:
|
||||
_api.check_in_list([None, "equal", "transform"], mode=mode)
|
||||
|
||||
def apply_aspect(self, position=None):
|
||||
self._update_viewlim()
|
||||
super().apply_aspect()
|
||||
|
||||
|
||||
@_api.deprecated("3.4", alternative="parasite_axes_class_factory")
|
||||
@functools.lru_cache(None)
|
||||
def parasite_axes_auxtrans_class_factory(axes_class=None):
|
||||
if axes_class is None:
|
||||
_api.warn_deprecated(
|
||||
"3.3", message="Support for passing None to "
|
||||
"parasite_axes_auxtrans_class_factory is deprecated since "
|
||||
"%(since)s and will be removed %(removal)s; explicitly pass the "
|
||||
"default ParasiteAxes class instead.")
|
||||
parasite_axes_class = ParasiteAxes
|
||||
elif not issubclass(axes_class, ParasiteAxesBase):
|
||||
parasite_axes_class = parasite_axes_class_factory(axes_class)
|
||||
else:
|
||||
parasite_axes_class = axes_class
|
||||
return type("%sParasiteAuxTrans" % parasite_axes_class.__name__,
|
||||
(ParasiteAxesAuxTransBase, parasite_axes_class),
|
||||
{'name': 'parasite_axes'})
|
||||
|
||||
|
||||
# Also deprecated.
|
||||
with _api.suppress_matplotlib_deprecation_warning():
|
||||
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes)
|
||||
|
||||
|
||||
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=Axes):
|
||||
"""
|
||||
Add a parasite axes to this host.
|
||||
|
||||
Despite this method's name, this should actually be thought of as an
|
||||
``add_parasite_axes`` method.
|
||||
|
||||
*tr* may be `.Transform`, in which case the following relation will
|
||||
hold: ``parasite.transData = tr + host.transData``. Alternatively, it
|
||||
may be None (the default), no special relationship will hold between
|
||||
the parasite's and the host's ``transData``.
|
||||
"""
|
||||
parasite_axes_class = parasite_axes_class_factory(axes_class)
|
||||
ax2 = parasite_axes_class(self, tr, viewlim_mode=viewlim_mode)
|
||||
# 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 _get_legend_handles(self, legend_handler_map=None):
|
||||
all_handles = super()._get_legend_handles()
|
||||
for ax in self.parasites:
|
||||
all_handles.extend(ax._get_legend_handles(legend_handler_map))
|
||||
return all_handles
|
||||
|
||||
def draw(self, renderer):
|
||||
|
||||
orig_artists = list(self.artists)
|
||||
orig_images = list(self.images)
|
||||
|
||||
if hasattr(self, "get_axes_locator"):
|
||||
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()
|
||||
else:
|
||||
self.apply_aspect()
|
||||
|
||||
rect = self.get_position()
|
||||
|
||||
for ax in self.parasites:
|
||||
ax.apply_aspect(rect)
|
||||
images, artists = ax.get_images_artists()
|
||||
self.images.extend(images)
|
||||
self.artists.extend(artists)
|
||||
|
||||
super().draw(renderer)
|
||||
self.artists = orig_artists
|
||||
self.images = orig_images
|
||||
|
||||
def cla(self):
|
||||
for ax in self.parasites:
|
||||
ax.cla()
|
||||
super().cla()
|
||||
|
||||
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._get_base_axes()
|
||||
ax = parasite_axes_class_factory(axes_class)(self, **kwargs)
|
||||
self.parasites.append(ax)
|
||||
ax._remove_method = self._remove_any_twin
|
||||
return ax
|
||||
|
||||
def _remove_any_twin(self, ax):
|
||||
self.parasites.remove(ax)
|
||||
restore = ["top", "right"]
|
||||
if ax._sharex:
|
||||
restore.remove("top")
|
||||
if ax._sharey:
|
||||
restore.remove("right")
|
||||
self.axis[tuple(restore)].set_visible(True)
|
||||
self.axis[tuple(restore)].toggle(ticklabels=False, label=False)
|
||||
|
||||
def get_tightbbox(self, renderer, 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])
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def host_axes_class_factory(axes_class=None):
|
||||
if axes_class is None:
|
||||
_api.warn_deprecated(
|
||||
"3.3", message="Support for passing None to host_axes_class is "
|
||||
"deprecated since %(since)s and will be removed %(removed)s; "
|
||||
"explicitly pass the default Axes class instead.")
|
||||
axes_class = Axes
|
||||
|
||||
def _get_base_axes(self):
|
||||
return axes_class
|
||||
|
||||
return type("%sHostAxes" % axes_class.__name__,
|
||||
(HostAxesBase, axes_class),
|
||||
{'_get_base_axes': _get_base_axes})
|
||||
|
||||
|
||||
def host_subplot_class_factory(axes_class):
|
||||
host_axes_class = host_axes_class_factory(axes_class)
|
||||
subplot_host_class = subplot_class_factory(host_axes_class)
|
||||
return subplot_host_class
|
||||
|
||||
|
||||
HostAxes = host_axes_class_factory(Axes)
|
||||
SubplotHost = subplot_class_factory(HostAxes)
|
||||
|
||||
|
||||
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`` 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)
|
||||
plt.draw_if_interactive()
|
||||
return ax
|
||||
|
||||
|
||||
def host_subplot(*args, axes_class=Axes, figure=None, **kwargs):
|
||||
"""
|
||||
Create a subplot that can act as a host to parasitic axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
figure : `matplotlib.figure.Figure`
|
||||
Figure to which the subplot will be added. Defaults to the current
|
||||
figure `.pyplot.gcf()`.
|
||||
|
||||
*args, **kwargs
|
||||
Will be passed on to the underlying ``Axes`` object creation.
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
host_subplot_class = host_subplot_class_factory(axes_class)
|
||||
if figure is None:
|
||||
figure = plt.gcf()
|
||||
ax = host_subplot_class(figure, *args, **kwargs)
|
||||
figure.add_subplot(ax)
|
||||
plt.draw_if_interactive()
|
||||
return ax
|
Reference in New Issue
Block a user