demo + utils venv
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
from . import axes_size as Size
|
||||
from .axes_divider import Divider, SubplotDivider, LocatableAxes, \
|
||||
make_axes_locatable
|
||||
from .axes_grid import Grid, ImageGrid, AxesGrid
|
||||
#from axes_divider import make_axes_locatable
|
||||
|
||||
from .parasite_axes import host_subplot, host_axes
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,595 @@
|
||||
from matplotlib import docstring, 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):
|
||||
@docstring.dedent
|
||||
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 : int or float
|
||||
width and height of the container, in pixels.
|
||||
|
||||
xdescent, ydescent : int or 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 : int or float, optional
|
||||
Padding around the child objects, in fraction of the font
|
||||
size. Defaults to 0.4.
|
||||
|
||||
borderpad : int or float, optional
|
||||
Border padding, in fraction of the font size.
|
||||
Defaults to 0.5.
|
||||
|
||||
prop : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font property used as a reference for paddings.
|
||||
|
||||
frameon : bool, optional
|
||||
If True, draw a box around this artists. Defaults to True.
|
||||
|
||||
**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):
|
||||
@docstring.dedent
|
||||
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 : int or float, optional
|
||||
Padding around the child objects, in fraction of the font
|
||||
size. Defaults to 0.4.
|
||||
|
||||
borderpad : int or float, optional
|
||||
Border padding, in fraction of the font size.
|
||||
Defaults to 0.5.
|
||||
|
||||
prop : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font property used as a reference for paddings.
|
||||
|
||||
frameon : bool, optional
|
||||
If True, draw a box around this artists. Defaults to True.
|
||||
|
||||
**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)
|
||||
|
||||
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
|
||||
child=self.drawing_area,
|
||||
prop=prop,
|
||||
frameon=frameon,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class AnchoredEllipse(AnchoredOffsetbox):
|
||||
@docstring.dedent
|
||||
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 : int or float
|
||||
Width and height of the ellipse, given in coordinates of
|
||||
*transform*.
|
||||
|
||||
angle : int or 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 : int or float, optional
|
||||
Padding around the ellipse, in fraction of the font size. Defaults
|
||||
to 0.1.
|
||||
|
||||
borderpad : int or float, optional
|
||||
Border padding, in fraction of the font size. Defaults to 0.1.
|
||||
|
||||
frameon : bool, optional
|
||||
If True, draw a box around the ellipse. Defaults to True.
|
||||
|
||||
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)
|
||||
|
||||
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
|
||||
child=self._box,
|
||||
prop=prop,
|
||||
frameon=frameon, **kwargs)
|
||||
|
||||
|
||||
class AnchoredSizeBar(AnchoredOffsetbox):
|
||||
@docstring.dedent
|
||||
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 : int or 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 : int or float, optional
|
||||
Padding around the label and size bar, in fraction of the font
|
||||
size. Defaults to 0.1.
|
||||
|
||||
borderpad : int or float, optional
|
||||
Border padding, in fraction of the font size.
|
||||
Defaults to 0.1.
|
||||
|
||||
sep : int or float, optional
|
||||
Separation between the label and the size bar, in points.
|
||||
Defaults to 2.
|
||||
|
||||
frameon : bool, optional
|
||||
If True, draw a box around the horizontal bar and label.
|
||||
Defaults to True.
|
||||
|
||||
size_vertical : int or float, optional
|
||||
Vertical length of the size bar, given in coordinates of
|
||||
*transform*. Defaults to 0.
|
||||
|
||||
color : str, optional
|
||||
Color for the size bar and label.
|
||||
Defaults to black.
|
||||
|
||||
label_top : bool, optional
|
||||
If True, the label will be over the size bar.
|
||||
Defaults to False.
|
||||
|
||||
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,
|
||||
minimumdescent=False,
|
||||
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)
|
||||
|
||||
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
|
||||
child=self._box,
|
||||
prop=fontproperties,
|
||||
frameon=frameon, **kwargs)
|
||||
|
||||
|
||||
class AnchoredDirectionArrows(AnchoredOffsetbox):
|
||||
@docstring.dedent
|
||||
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 : string
|
||||
Label text for the x and y arrows
|
||||
|
||||
length : int or float, optional
|
||||
Length of the arrow, given in coordinates of
|
||||
*transform*.
|
||||
Defaults to 0.15.
|
||||
|
||||
fontsize : int, optional
|
||||
Size of label strings, given in coordinates of *transform*.
|
||||
Defaults to 0.08.
|
||||
|
||||
loc : int, optional
|
||||
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
|
||||
|
||||
Defaults to 2.
|
||||
|
||||
angle : int or float, optional
|
||||
The angle of the arrows in degrees.
|
||||
Defaults to 0.
|
||||
|
||||
aspect_ratio : int or float, optional
|
||||
The ratio of the length of arrow_x and arrow_y.
|
||||
Negative numbers can be used to change the direction.
|
||||
Defaults to 1.
|
||||
|
||||
pad : int or float, optional
|
||||
Padding around the labels and arrows, in fraction of the font
|
||||
size. Defaults to 0.4.
|
||||
|
||||
borderpad : int or float, optional
|
||||
Border padding, in fraction of the font size.
|
||||
Defaults to 0.4.
|
||||
|
||||
frameon : bool, optional
|
||||
If True, draw a box around the arrows and labels.
|
||||
Defaults to False.
|
||||
|
||||
color : str, optional
|
||||
Color for the arrows and labels.
|
||||
Defaults to white.
|
||||
|
||||
alpha : int or float, optional
|
||||
Alpha values of the arrows and labels
|
||||
Defaults to 1.
|
||||
|
||||
sep_x, sep_y : int or float, optional
|
||||
Separation between the arrows and labels in coordinates of
|
||||
*transform*. Defaults to 0.01 and 0.
|
||||
|
||||
fontproperties : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font properties for the label text.
|
||||
|
||||
back_length : float, optional
|
||||
Fraction of the arrow behind the arrow crossing.
|
||||
Defaults to 0.15.
|
||||
|
||||
head_width : int or float, optional
|
||||
Width of arrow head, sent to ArrowStyle.
|
||||
Defaults to 10.
|
||||
|
||||
head_length : int or float, optional
|
||||
Length of arrow head, sent to ArrowStyle.
|
||||
Defaults to 15.
|
||||
|
||||
tail_width : int or float, optional
|
||||
Width of arrow tail, sent to ArrowStyle.
|
||||
Defaults to 2.
|
||||
|
||||
text_props, arrow_props : dict
|
||||
Properties of the text and arrows, passed to
|
||||
:class:`matplotlib.text.TextPath` and
|
||||
`matplotlib.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.text.TextPath`
|
||||
Path for arrow labels
|
||||
|
||||
p_x, p_y : `matplotlib.patches.PathPatch`
|
||||
Patch for arrow labels
|
||||
|
||||
box : `matplotlib.offsetbox.AuxTransformBox`
|
||||
Container for the arrows and labels.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If *prop* is passed as a keyword argument, but *fontproperties* is
|
||||
not, then *prop* is 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)
|
||||
|
||||
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
|
||||
child=self.box,
|
||||
frameon=frameon, **kwargs)
|
||||
@@ -0,0 +1,917 @@
|
||||
"""
|
||||
The axes_divider module provides helper classes to adjust the positions of
|
||||
multiple axes at drawing time.
|
||||
|
||||
Divider: this is the class that is used to calculate the axes
|
||||
position. It divides the given rectangular area into several sub
|
||||
rectangles. You initialize the divider by setting the horizontal
|
||||
and vertical lists of sizes that the division will be based on. You
|
||||
then use the new_locator method, whose return value is a callable
|
||||
object that can be used to set the axes_locator of the axes.
|
||||
"""
|
||||
|
||||
import functools
|
||||
|
||||
import matplotlib.transforms as mtransforms
|
||||
from matplotlib import cbook
|
||||
from matplotlib.axes import SubplotBase
|
||||
from . import axes_size as Size
|
||||
|
||||
|
||||
class Divider(object):
|
||||
"""
|
||||
This class calculates the axes position. It
|
||||
divides the given rectangular area into several
|
||||
sub-rectangles. You initialize the divider by setting the
|
||||
horizontal and vertical lists of sizes
|
||||
(:mod:`mpl_toolkits.axes_grid.axes_size`) that the division will
|
||||
be based on. You then use the new_locator method to create 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_grid.axes_size`
|
||||
sizes for horizontal division
|
||||
vertical : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
|
||||
sizes for vertical division
|
||||
aspect : bool
|
||||
if True, the 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):
|
||||
|
||||
from .axes_size import AddList
|
||||
|
||||
vsize = AddList(self.get_vertical())
|
||||
hsize = 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 s in l:
|
||||
for _rs, _as in l:
|
||||
#_rs, _as = s.get_size(renderer)
|
||||
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 anchor in mtransforms.Bbox.coefs or len(anchor) == 2:
|
||||
self._anchor = anchor
|
||||
else:
|
||||
raise ValueError('argument must be among %s' %
|
||||
', '.join(mtransforms.BBox.coefs))
|
||||
|
||||
def get_anchor(self):
|
||||
"return the anchor"
|
||||
return self._anchor
|
||||
|
||||
def set_horizontal(self, h):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
h : list of :mod:`~mpl_toolkits.axes_grid.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_grid.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):
|
||||
"""
|
||||
Returns a new locator
|
||||
(:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
|
||||
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:
|
||||
raise ValueError("the position must be one of left," +
|
||||
" right, bottom, or top")
|
||||
|
||||
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(object):
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
from matplotlib.gridspec import SubplotSpec, GridSpec
|
||||
|
||||
|
||||
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 : :class:`matplotlib.figure.Figure`
|
||||
args : tuple (*numRows*, *numCols*, *plotNum*)
|
||||
The array of subplots in the figure has dimensions *numRows*,
|
||||
*numCols*, and *plotNum* is the number of the subplot
|
||||
being created. *plotNum* starts at 1 in the upper left
|
||||
corner and increases to the right.
|
||||
|
||||
If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the
|
||||
decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*.
|
||||
"""
|
||||
|
||||
self.figure = fig
|
||||
|
||||
if len(args) == 1:
|
||||
if isinstance(args[0], SubplotSpec):
|
||||
self._subplotspec = args[0]
|
||||
else:
|
||||
try:
|
||||
s = str(int(args[0]))
|
||||
rows, cols, num = map(int, s)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'Single argument to subplot must be a 3-digit integer')
|
||||
self._subplotspec = GridSpec(rows, cols)[num-1]
|
||||
# num - 1 for converting from MATLAB to python indexing
|
||||
elif len(args) == 3:
|
||||
rows, cols, num = args
|
||||
rows = int(rows)
|
||||
cols = int(cols)
|
||||
if isinstance(num, tuple) and len(num) == 2:
|
||||
num = [int(n) for n in num]
|
||||
self._subplotspec = GridSpec(rows, cols)[num[0]-1:num[1]]
|
||||
else:
|
||||
self._subplotspec = GridSpec(rows, cols)[int(num)-1]
|
||||
# num - 1 for converting from MATLAB to python indexing
|
||||
else:
|
||||
raise ValueError('Illegal argument(s) to subplot: %s' % (args,))
|
||||
|
||||
# total = rows*cols
|
||||
# num -= 1 # convert from matlab to python indexing
|
||||
# # i.e., num in range(0,total)
|
||||
# if num >= total:
|
||||
# raise ValueError( 'Subplot number exceeds total subplots')
|
||||
# self._rows = rows
|
||||
# self._cols = cols
|
||||
# self._num = num
|
||||
|
||||
# self.update_params()
|
||||
|
||||
# sets self.fixbox
|
||||
self.update_params()
|
||||
|
||||
pos = self.figbox.bounds
|
||||
|
||||
Divider.__init__(self, fig, pos, horizontal or [], vertical or [],
|
||||
aspect=aspect, anchor=anchor)
|
||||
|
||||
def get_position(self):
|
||||
"return the bounds of the subplot box"
|
||||
|
||||
self.update_params() # update self.figbox
|
||||
return self.figbox.bounds
|
||||
|
||||
# def update_params(self):
|
||||
# 'update the subplot position from fig.subplotpars'
|
||||
|
||||
# rows = self._rows
|
||||
# cols = self._cols
|
||||
# num = self._num
|
||||
|
||||
# pars = self.figure.subplotpars
|
||||
# left = pars.left
|
||||
# right = pars.right
|
||||
# bottom = pars.bottom
|
||||
# top = pars.top
|
||||
# wspace = pars.wspace
|
||||
# hspace = pars.hspace
|
||||
# totWidth = right-left
|
||||
# totHeight = top-bottom
|
||||
|
||||
# figH = totHeight/(rows + hspace*(rows-1))
|
||||
# sepH = hspace*figH
|
||||
|
||||
# figW = totWidth/(cols + wspace*(cols-1))
|
||||
# sepW = wspace*figW
|
||||
|
||||
# rowNum, colNum = divmod(num, cols)
|
||||
|
||||
# figBottom = top - (rowNum+1)*figH - rowNum*sepH
|
||||
# figLeft = left + colNum*(figW + sepW)
|
||||
|
||||
# self.figbox = mtransforms.Bbox.from_bounds(figLeft, figBottom,
|
||||
# figW, figH)
|
||||
|
||||
def update_params(self):
|
||||
'update the subplot position from fig.subplotpars'
|
||||
|
||||
self.figbox = self.get_subplotspec().get_position(self.figure)
|
||||
|
||||
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
|
||||
|
||||
# COVERAGE NOTE: Never used internally or from examples
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
Divider.__init__(self, 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_grid.axes_size` or float or string
|
||||
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_grid.axes_size` or float or string
|
||||
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:
|
||||
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_grid.axes_size` or float or string
|
||||
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_grid.axes_size` or float or string
|
||||
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:
|
||||
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:
|
||||
raise ValueError("the position must be one of left," +
|
||||
" right, bottom, or top")
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, fig, *args, **kwargs):
|
||||
SubplotDivider.__init__(self, fig, *args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _determine_karray(equivalent_sizes, appended_sizes,
|
||||
max_equivalent_size,
|
||||
total_appended_size):
|
||||
|
||||
n = len(equivalent_sizes)
|
||||
import numpy as np
|
||||
A = np.mat(np.zeros((n+1, n+1), dtype="d"))
|
||||
B = np.zeros((n+1), dtype="d")
|
||||
# AxK = B
|
||||
|
||||
# populated A
|
||||
for i, (r, a) in enumerate(equivalent_sizes):
|
||||
A[i, i] = r
|
||||
A[i, -1] = -1
|
||||
B[i] = -a
|
||||
A[-1, :-1] = [r for r, a in appended_sizes]
|
||||
B[-1] = total_appended_size - sum([a for rs, a in appended_sizes])
|
||||
|
||||
karray_H = (A.I*np.mat(B).T).A1
|
||||
karray = karray_H[:-1]
|
||||
H = karray_H[-1]
|
||||
|
||||
if H > max_equivalent_size:
|
||||
karray = ((max_equivalent_size -
|
||||
np.array([a for r, a in equivalent_sizes]))
|
||||
/ np.array([r for r, a in equivalent_sizes]))
|
||||
return karray
|
||||
|
||||
@staticmethod
|
||||
def _calc_offsets(appended_sizes, karray):
|
||||
offsets = [0.]
|
||||
|
||||
#for s in l:
|
||||
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):
|
||||
"""
|
||||
returns a new locator
|
||||
(:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
|
||||
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):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
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):
|
||||
"""
|
||||
returns a new locator
|
||||
(:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
|
||||
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)
|
||||
|
||||
|
||||
@cbook.deprecated('3.0',
|
||||
addendum=' There is no alternative. Deriving from '
|
||||
'matplotlib.axes.Axes provides this functionality '
|
||||
'already.')
|
||||
class LocatableAxesBase(object):
|
||||
pass
|
||||
|
||||
|
||||
@cbook.deprecated('3.0',
|
||||
addendum=' There is no alternative. Classes derived from '
|
||||
'matplotlib.axes.Axes provide this functionality '
|
||||
'already.')
|
||||
def locatable_axes_factory(axes_class):
|
||||
return axes_class
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
from .mpl_axes import Axes as _Axes
|
||||
|
||||
|
||||
@cbook.deprecated('3.0',
|
||||
alternative='mpl_toolkits.axes_grid1.mpl_axes.Axes')
|
||||
class Axes(_Axes):
|
||||
pass
|
||||
|
||||
|
||||
@cbook.deprecated('3.0',
|
||||
alternative='mpl_toolkits.axes_grid1.mpl_axes.Axes')
|
||||
class LocatableAxes(_Axes):
|
||||
pass
|
||||
@@ -0,0 +1,761 @@
|
||||
from numbers import Number
|
||||
|
||||
import matplotlib.axes as maxes
|
||||
import matplotlib.ticker as ticker
|
||||
from matplotlib.gridspec import SubplotSpec
|
||||
|
||||
from .axes_divider import Size, SubplotDivider, Divider
|
||||
from .colorbar import Colorbar
|
||||
from .mpl_axes import Axes
|
||||
|
||||
|
||||
def _extend_axes_pad(value):
|
||||
# Check whether a list/tuple/array or scalar has been passed
|
||||
ret = value
|
||||
if not hasattr(ret, "__getitem__"):
|
||||
ret = (value, value)
|
||||
return ret
|
||||
|
||||
|
||||
def _tick_only(ax, bottom_on, left_on):
|
||||
bottom_off = not bottom_on
|
||||
left_off = not left_on
|
||||
# [l.set_visible(bottom_off) for l in ax.get_xticklabels()]
|
||||
# [l.set_visible(left_off) for l in ax.get_yticklabels()]
|
||||
# ax.xaxis.label.set_visible(bottom_off)
|
||||
# ax.yaxis.label.set_visible(left_off)
|
||||
ax.axis["bottom"].toggle(ticklabels=bottom_off, label=bottom_off)
|
||||
ax.axis["left"].toggle(ticklabels=left_off, label=left_off)
|
||||
|
||||
|
||||
class CbarAxesBase(object):
|
||||
|
||||
def colorbar(self, mappable, *, locator=None, **kwargs):
|
||||
|
||||
if locator is None:
|
||||
if "ticks" not in kwargs:
|
||||
kwargs["ticks"] = ticker.MaxNLocator(5)
|
||||
if locator is not None:
|
||||
if "ticks" in kwargs:
|
||||
raise ValueError("Either *locator* or *ticks* need" +
|
||||
" to be given, not both")
|
||||
else:
|
||||
kwargs["ticks"] = locator
|
||||
|
||||
if self.orientation in ["top", "bottom"]:
|
||||
orientation = "horizontal"
|
||||
else:
|
||||
orientation = "vertical"
|
||||
|
||||
cb = Colorbar(self, mappable, orientation=orientation, **kwargs)
|
||||
self._config_axes()
|
||||
|
||||
def on_changed(m):
|
||||
cb.set_cmap(m.get_cmap())
|
||||
cb.set_clim(m.get_clim())
|
||||
cb.update_bruteforce(m)
|
||||
|
||||
self.cbid = mappable.callbacksSM.connect('changed', on_changed)
|
||||
mappable.colorbar = cb
|
||||
|
||||
self.locator = cb.cbar_axis.get_major_locator()
|
||||
|
||||
return cb
|
||||
|
||||
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)
|
||||
|
||||
# for axis in ax.axis.values():
|
||||
# axis.major_ticks.set_visible(False)
|
||||
# axis.minor_ticks.set_visible(False)
|
||||
# axis.major_ticklabels.set_visible(False)
|
||||
# axis.minor_ticklabels.set_visible(False)
|
||||
# axis.label.set_visible(False)
|
||||
|
||||
# axis = ax.axis[self.orientation]
|
||||
# axis.major_ticks.set_visible(True)
|
||||
# axis.minor_ticks.set_visible(True)
|
||||
|
||||
#axis.major_ticklabels.set_size(
|
||||
# int(axis.major_ticklabels.get_size()*.9))
|
||||
#axis.major_tick_pad = 3
|
||||
|
||||
# axis.major_ticklabels.set_visible(b)
|
||||
# axis.minor_ticklabels.set_visible(b)
|
||||
# axis.label.set_visible(b)
|
||||
|
||||
def toggle_label(self, b):
|
||||
self._default_label_on = b
|
||||
axis = self.axis[self.orientation]
|
||||
axis.toggle(ticklabels=b, label=b)
|
||||
#axis.major_ticklabels.set_visible(b)
|
||||
#axis.minor_ticklabels.set_visible(b)
|
||||
#axis.label.set_visible(b)
|
||||
|
||||
|
||||
class CbarAxes(CbarAxesBase, Axes):
|
||||
def __init__(self, *args, orientation, **kwargs):
|
||||
self.orientation = orientation
|
||||
self._default_label_on = True
|
||||
self.locator = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def cla(self):
|
||||
super().cla()
|
||||
self._config_axes()
|
||||
|
||||
|
||||
class Grid(object):
|
||||
"""
|
||||
A class that creates a grid of Axes. In matplotlib, the axes
|
||||
location (and size) is specified in the normalized figure
|
||||
coordinates. This may not be ideal for images that needs to be
|
||||
displayed with a given aspect ratio. For example, displaying
|
||||
images of a same size with some fixed padding between them cannot
|
||||
be easily done in matplotlib. AxesGrid is used in such case.
|
||||
"""
|
||||
|
||||
_defaultAxesClass = Axes
|
||||
|
||||
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,
|
||||
#aspect=True,
|
||||
label_mode="L",
|
||||
axes_class=None,
|
||||
):
|
||||
"""
|
||||
Build an :class:`Grid` instance with a grid nrows*ncols
|
||||
:class:`~matplotlib.axes.Axes` in
|
||||
:class:`~matplotlib.figure.Figure` *fig* with
|
||||
*rect=[left, bottom, width, height]* (in
|
||||
:class:`~matplotlib.figure.Figure` coordinates) or
|
||||
the subplot position code (e.g., "121").
|
||||
|
||||
Optional keyword arguments:
|
||||
|
||||
================ ======== =========================================
|
||||
Keyword Default Description
|
||||
================ ======== =========================================
|
||||
direction "row" [ "row" | "column" ]
|
||||
axes_pad 0.02 float| pad between axes given in inches
|
||||
or tuple-like of floats,
|
||||
(horizontal padding, vertical padding)
|
||||
add_all True bool
|
||||
share_all False bool
|
||||
share_x True bool
|
||||
share_y True bool
|
||||
label_mode "L" [ "L" | "1" | "all" ]
|
||||
axes_class None a type object which must be a subclass
|
||||
of :class:`~matplotlib.axes.Axes`
|
||||
================ ======== =========================================
|
||||
"""
|
||||
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._init_axes_pad(axes_pad)
|
||||
|
||||
if direction not in ["column", "row"]:
|
||||
raise Exception("")
|
||||
|
||||
self._direction = direction
|
||||
|
||||
if axes_class is None:
|
||||
axes_class = self._defaultAxesClass
|
||||
axes_class_args = {}
|
||||
else:
|
||||
if (isinstance(axes_class, type)
|
||||
and issubclass(axes_class,
|
||||
self._defaultAxesClass.Axes)):
|
||||
axes_class_args = {}
|
||||
else:
|
||||
axes_class, axes_class_args = axes_class
|
||||
|
||||
self.axes_all = []
|
||||
self.axes_column = [[] for _ in range(self._ncols)]
|
||||
self.axes_row = [[] for _ in range(self._nrows)]
|
||||
|
||||
h = []
|
||||
v = []
|
||||
if isinstance(rect, (str, Number)):
|
||||
self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v,
|
||||
aspect=False)
|
||||
elif isinstance(rect, SubplotSpec):
|
||||
self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v,
|
||||
aspect=False)
|
||||
elif len(rect) == 3:
|
||||
kw = dict(horizontal=h, vertical=v, aspect=False)
|
||||
self._divider = SubplotDivider(fig, *rect, **kw)
|
||||
elif len(rect) == 4:
|
||||
self._divider = Divider(fig, rect, horizontal=h, vertical=v,
|
||||
aspect=False)
|
||||
else:
|
||||
raise Exception("")
|
||||
|
||||
rect = self._divider.get_position()
|
||||
|
||||
# reference axes
|
||||
self._column_refax = [None for _ in range(self._ncols)]
|
||||
self._row_refax = [None for _ in range(self._nrows)]
|
||||
self._refax = None
|
||||
|
||||
for i in range(self.ngrids):
|
||||
|
||||
col, row = self._get_col_row(i)
|
||||
|
||||
if share_all:
|
||||
sharex = self._refax
|
||||
sharey = self._refax
|
||||
else:
|
||||
if share_x:
|
||||
sharex = self._column_refax[col]
|
||||
else:
|
||||
sharex = None
|
||||
|
||||
if share_y:
|
||||
sharey = self._row_refax[row]
|
||||
else:
|
||||
sharey = None
|
||||
|
||||
ax = axes_class(fig, rect, sharex=sharex, sharey=sharey,
|
||||
**axes_class_args)
|
||||
|
||||
if share_all:
|
||||
if self._refax is None:
|
||||
self._refax = ax
|
||||
else:
|
||||
if sharex is None:
|
||||
self._column_refax[col] = ax
|
||||
if sharey is None:
|
||||
self._row_refax[row] = ax
|
||||
|
||||
self.axes_all.append(ax)
|
||||
self.axes_column[col].append(ax)
|
||||
self.axes_row[row].append(ax)
|
||||
|
||||
self.axes_llc = self.axes_column[0][-1]
|
||||
|
||||
self._update_locators()
|
||||
|
||||
if add_all:
|
||||
for ax in self.axes_all:
|
||||
fig.add_axes(ax)
|
||||
|
||||
self.set_label_mode(label_mode)
|
||||
|
||||
def _init_axes_pad(self, axes_pad):
|
||||
axes_pad = _extend_axes_pad(axes_pad)
|
||||
self._axes_pad = axes_pad
|
||||
|
||||
self._horiz_pad_size = Size.Fixed(axes_pad[0])
|
||||
self._vert_pad_size = Size.Fixed(axes_pad[1])
|
||||
|
||||
def _update_locators(self):
|
||||
|
||||
h = []
|
||||
|
||||
h_ax_pos = []
|
||||
|
||||
for _ in self._column_refax:
|
||||
#if h: h.append(Size.Fixed(self._axes_pad))
|
||||
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 self._row_refax[::-1]:
|
||||
#if v: v.append(Size.Fixed(self._axes_pad))
|
||||
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):
|
||||
"""
|
||||
get geometry of the grid. Returns a tuple of two integer,
|
||||
representing number of rows and number of columns.
|
||||
"""
|
||||
return self._nrows, self._ncols
|
||||
|
||||
def set_axes_pad(self, axes_pad):
|
||||
"set axes_pad"
|
||||
self._axes_pad = axes_pad
|
||||
|
||||
# These two lines actually differ from ones in _init_axes_pad
|
||||
self._horiz_pad_size.fixed_size = axes_pad[0]
|
||||
self._vert_pad_size.fixed_size = axes_pad[1]
|
||||
|
||||
def get_axes_pad(self):
|
||||
"""
|
||||
get axes_pad
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple
|
||||
Padding in inches, (horizontal pad, vertical pad)
|
||||
"""
|
||||
return self._axes_pad
|
||||
|
||||
def set_aspect(self, aspect):
|
||||
"set aspect"
|
||||
self._divider.set_aspect(aspect)
|
||||
|
||||
def get_aspect(self):
|
||||
"get aspect"
|
||||
return self._divider.get_aspect()
|
||||
|
||||
def set_label_mode(self, mode):
|
||||
"set label_mode"
|
||||
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()
|
||||
# from axes_size import AddList
|
||||
|
||||
# vsize = AddList(self._divider.get_vertical())
|
||||
# hsize = AddList(self._divider.get_horizontal())
|
||||
|
||||
# return vsize, hsize
|
||||
|
||||
|
||||
class ImageGrid(Grid):
|
||||
"""
|
||||
A class that creates a grid of Axes. In matplotlib, the axes
|
||||
location (and size) is specified in the normalized figure
|
||||
coordinates. This may not be ideal for images that needs to be
|
||||
displayed with a given aspect ratio. For example, displaying
|
||||
images of a same size with some fixed padding between them cannot
|
||||
be easily done in matplotlib. ImageGrid is used in such case.
|
||||
"""
|
||||
|
||||
_defaultCbarAxesClass = CbarAxes
|
||||
|
||||
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,
|
||||
):
|
||||
"""
|
||||
Build an :class:`ImageGrid` instance with a grid nrows*ncols
|
||||
:class:`~matplotlib.axes.Axes` in
|
||||
:class:`~matplotlib.figure.Figure` *fig* with
|
||||
*rect=[left, bottom, width, height]* (in
|
||||
:class:`~matplotlib.figure.Figure` coordinates) or
|
||||
the subplot position code (e.g., "121").
|
||||
|
||||
Optional keyword arguments:
|
||||
|
||||
================ ======== =========================================
|
||||
Keyword Default Description
|
||||
================ ======== =========================================
|
||||
direction "row" [ "row" | "column" ]
|
||||
axes_pad 0.02 float| pad between axes given in inches
|
||||
or tuple-like of floats,
|
||||
(horizontal padding, vertical padding)
|
||||
add_all True bool
|
||||
share_all False bool
|
||||
aspect True bool
|
||||
label_mode "L" [ "L" | "1" | "all" ]
|
||||
cbar_mode None [ "each" | "single" | "edge" ]
|
||||
cbar_location "right" [ "left" | "right" | "bottom" | "top" ]
|
||||
cbar_pad None
|
||||
cbar_size "5%"
|
||||
cbar_set_cax True bool
|
||||
axes_class None a type object which must be a subclass
|
||||
of axes_grid's subclass of
|
||||
:class:`~matplotlib.axes.Axes`
|
||||
================ ======== =========================================
|
||||
|
||||
*cbar_set_cax* : if True, each axes in the grid has a cax
|
||||
attribute that is bind to associated cbar_axes.
|
||||
"""
|
||||
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
|
||||
|
||||
axes_pad = _extend_axes_pad(axes_pad)
|
||||
self._axes_pad = axes_pad
|
||||
|
||||
self._colorbar_mode = cbar_mode
|
||||
self._colorbar_location = cbar_location
|
||||
if cbar_pad is None:
|
||||
# horizontal or vertical arrangement?
|
||||
if cbar_location in ("left", "right"):
|
||||
self._colorbar_pad = axes_pad[0]
|
||||
else:
|
||||
self._colorbar_pad = axes_pad[1]
|
||||
else:
|
||||
self._colorbar_pad = cbar_pad
|
||||
|
||||
self._colorbar_size = cbar_size
|
||||
|
||||
self._init_axes_pad(axes_pad)
|
||||
|
||||
if direction not in ["column", "row"]:
|
||||
raise Exception("")
|
||||
|
||||
self._direction = direction
|
||||
|
||||
if axes_class is None:
|
||||
axes_class = self._defaultAxesClass
|
||||
axes_class_args = {}
|
||||
else:
|
||||
if isinstance(axes_class, maxes.Axes):
|
||||
axes_class_args = {}
|
||||
else:
|
||||
axes_class, axes_class_args = axes_class
|
||||
|
||||
self.axes_all = []
|
||||
self.axes_column = [[] for _ in range(self._ncols)]
|
||||
self.axes_row = [[] for _ in range(self._nrows)]
|
||||
|
||||
self.cbar_axes = []
|
||||
|
||||
h = []
|
||||
v = []
|
||||
if isinstance(rect, (str, Number)):
|
||||
self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v,
|
||||
aspect=aspect)
|
||||
elif isinstance(rect, SubplotSpec):
|
||||
self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v,
|
||||
aspect=aspect)
|
||||
elif len(rect) == 3:
|
||||
kw = dict(horizontal=h, vertical=v, aspect=aspect)
|
||||
self._divider = SubplotDivider(fig, *rect, **kw)
|
||||
elif len(rect) == 4:
|
||||
self._divider = Divider(fig, rect, horizontal=h, vertical=v,
|
||||
aspect=aspect)
|
||||
else:
|
||||
raise Exception("")
|
||||
|
||||
rect = self._divider.get_position()
|
||||
|
||||
# reference axes
|
||||
self._column_refax = [None for _ in range(self._ncols)]
|
||||
self._row_refax = [None for _ in range(self._nrows)]
|
||||
self._refax = None
|
||||
|
||||
for i in range(self.ngrids):
|
||||
|
||||
col, row = self._get_col_row(i)
|
||||
|
||||
if share_all:
|
||||
if self.axes_all:
|
||||
sharex = self.axes_all[0]
|
||||
sharey = self.axes_all[0]
|
||||
else:
|
||||
sharex = None
|
||||
sharey = None
|
||||
else:
|
||||
sharex = self._column_refax[col]
|
||||
sharey = self._row_refax[row]
|
||||
|
||||
ax = axes_class(fig, rect, sharex=sharex, sharey=sharey,
|
||||
**axes_class_args)
|
||||
|
||||
self.axes_all.append(ax)
|
||||
self.axes_column[col].append(ax)
|
||||
self.axes_row[row].append(ax)
|
||||
|
||||
if share_all:
|
||||
if self._refax is None:
|
||||
self._refax = ax
|
||||
if sharex is None:
|
||||
self._column_refax[col] = ax
|
||||
if sharey is None:
|
||||
self._row_refax[row] = ax
|
||||
|
||||
cax = self._defaultCbarAxesClass(fig, rect,
|
||||
orientation=self._colorbar_location)
|
||||
self.cbar_axes.append(cax)
|
||||
|
||||
self.axes_llc = self.axes_column[0][-1]
|
||||
|
||||
self._update_locators()
|
||||
|
||||
if add_all:
|
||||
for ax in self.axes_all+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
|
||||
|
||||
self.set_label_mode(label_mode)
|
||||
|
||||
def _update_locators(self):
|
||||
|
||||
h = []
|
||||
v = []
|
||||
|
||||
h_ax_pos = []
|
||||
h_cb_pos = []
|
||||
if (self._colorbar_mode == "single" and
|
||||
self._colorbar_location in ('left', 'bottom')):
|
||||
if self._colorbar_location == "left":
|
||||
#sz = Size.Fraction(Size.AxesX(self.axes_llc), self._nrows)
|
||||
sz = Size.Fraction(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 self._colorbar_location == "bottom":
|
||||
#sz = Size.Fraction(Size.AxesY(self.axes_llc), self._ncols)
|
||||
sz = Size.Fraction(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) # Size.Fixed(self._axes_pad))
|
||||
|
||||
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 (self._colorbar_mode == "each" or
|
||||
(self._colorbar_mode == 'edge' and
|
||||
col == 0)) and self._colorbar_location == "left":
|
||||
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 ((self._colorbar_mode == "each" or
|
||||
(self._colorbar_mode == 'edge' and
|
||||
col == self._ncols - 1)) and
|
||||
self._colorbar_location == "right"):
|
||||
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) # Size.Fixed(self._axes_pad))
|
||||
|
||||
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 (self._colorbar_mode == "each" or
|
||||
(self._colorbar_mode == 'edge' and
|
||||
row == 0)) and self._colorbar_location == "bottom":
|
||||
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 ((self._colorbar_mode == "each" or
|
||||
(self._colorbar_mode == 'edge' and
|
||||
row == self._nrows - 1)) and
|
||||
self._colorbar_location == "top"):
|
||||
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=4*col,
|
||||
# ny=2*(self._nrows - row - 1))
|
||||
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 self._colorbar_mode == "each":
|
||||
if self._colorbar_location in ("right", "left"):
|
||||
locator = self._divider.new_locator(
|
||||
nx=h_cb_pos[col], ny=v_ax_pos[self._nrows - 1 - row])
|
||||
|
||||
elif self._colorbar_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 self._colorbar_mode == 'edge':
|
||||
if ((self._colorbar_location == 'left' and col == 0) or
|
||||
(self._colorbar_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 ((self._colorbar_location == 'bottom' and
|
||||
row == self._nrows - 1) or
|
||||
(self._colorbar_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 self._colorbar_mode == "single":
|
||||
if self._colorbar_location == "right":
|
||||
#sz = Size.Fraction(Size.AxesX(self.axes_llc), self._nrows)
|
||||
sz = Size.Fraction(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 self._colorbar_location == "top":
|
||||
#sz = Size.Fraction(Size.AxesY(self.axes_llc), self._ncols)
|
||||
sz = Size.Fraction(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 self._colorbar_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 self._colorbar_mode == "each":
|
||||
for i in range(self.ngrids):
|
||||
self.cbar_axes[i].set_visible(True)
|
||||
elif self._colorbar_mode == "edge":
|
||||
if self._colorbar_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,220 @@
|
||||
import numpy as np
|
||||
|
||||
from .axes_divider import make_axes_locatable, Size
|
||||
from .mpl_axes import Axes
|
||||
|
||||
|
||||
def make_rgb_axes(ax, pad=0.01, axes_class=None, add_all=True):
|
||||
"""
|
||||
pad : fraction of the axes height.
|
||||
"""
|
||||
|
||||
divider = make_axes_locatable(ax)
|
||||
|
||||
pad_size = Size.Fraction(pad, Size.AxesY(ax))
|
||||
|
||||
xsize = Size.Fraction((1.-2.*pad)/3., Size.AxesX(ax))
|
||||
ysize = Size.Fraction((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)
|
||||
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
|
||||
|
||||
|
||||
def imshow_rgb(ax, r, g, b, **kwargs):
|
||||
ny, nx = r.shape
|
||||
R = np.zeros([ny, nx, 3], dtype="d")
|
||||
R[:,:,0] = r
|
||||
G = np.zeros_like(R)
|
||||
G[:,:,1] = g
|
||||
B = np.zeros_like(R)
|
||||
B[:,:,2] = b
|
||||
|
||||
RGB = R + G + B
|
||||
|
||||
im_rgb = ax.imshow(RGB, **kwargs)
|
||||
|
||||
return im_rgb
|
||||
|
||||
|
||||
class RGBAxesBase(object):
|
||||
"""base class for a 4-panel imshow (RGB, R, G, B)
|
||||
|
||||
Layout:
|
||||
+---------------+-----+
|
||||
| | R |
|
||||
+ +-----+
|
||||
| RGB | G |
|
||||
+ +-----+
|
||||
| | B |
|
||||
+---------------+-----+
|
||||
|
||||
Attributes
|
||||
----------
|
||||
_defaultAxesClass : matplotlib.axes.Axes
|
||||
defaults to 'Axes' in RGBAxes child class.
|
||||
No default in abstract base class
|
||||
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
|
||||
"""
|
||||
def __init__(self, *args, pad=0, add_all=True, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
pad : float
|
||||
fraction of the axes height to put as padding.
|
||||
defaults to 0.0
|
||||
add_all : bool
|
||||
True: Add the {rgb, r, g, b} axes to the figure
|
||||
defaults to True.
|
||||
axes_class : matplotlib.axes.Axes
|
||||
|
||||
kl :
|
||||
Unpacked into axes_class() init for RGB
|
||||
kwargs :
|
||||
Unpacked into axes_class() init for RGB, R, G, B axes
|
||||
"""
|
||||
try:
|
||||
axes_class = kwargs.pop("axes_class", self._defaultAxesClass)
|
||||
except AttributeError:
|
||||
raise AttributeError(
|
||||
'A subclass of RGBAxesBase must have a _defaultAxesClass '
|
||||
'attribute. If you are not sure which axes class to use, '
|
||||
'consider using mpl_toolkits.axes_grid1.mpl_axes.Axes.'
|
||||
)
|
||||
|
||||
ax = axes_class(*args, **kwargs)
|
||||
|
||||
divider = make_axes_locatable(ax)
|
||||
|
||||
pad_size = Size.Fraction(pad, Size.AxesY(ax))
|
||||
|
||||
xsize = Size.Fraction((1.-2.*pad)/3., Size.AxesX(ax))
|
||||
ysize = Size.Fraction((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 = []
|
||||
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)
|
||||
ax1.axis[:].toggle(ticklabels=False)
|
||||
ax_rgb.append(ax1)
|
||||
|
||||
self.RGB = ax
|
||||
self.R, self.G, self.B = ax_rgb
|
||||
|
||||
if add_all:
|
||||
fig = ax.get_figure()
|
||||
fig.add_axes(ax)
|
||||
self.add_RGB_to_figure()
|
||||
|
||||
self._config_axes()
|
||||
|
||||
def _config_axes(self, line_color='w', marker_edge_color='w'):
|
||||
"""Set the line color and ticks for the axes
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line_color : any matplotlib color
|
||||
marker_edge_color : any matplotlib color
|
||||
"""
|
||||
for ax1 in [self.RGB, self.R, self.G, self.B]:
|
||||
ax1.axis[:].line.set_color(line_color)
|
||||
ax1.axis[:].major_ticks.set_markeredgecolor(marker_edge_color)
|
||||
|
||||
def add_RGB_to_figure(self):
|
||||
"""Add the 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 : array-like
|
||||
The red array
|
||||
g : array-like
|
||||
The green array
|
||||
b : array-like
|
||||
The blue array
|
||||
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('Input shapes do not match.'
|
||||
'\nr.shape = {}'
|
||||
'\ng.shape = {}'
|
||||
'\nb.shape = {}'
|
||||
.format(r.shape, g.shape, b.shape))
|
||||
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
|
||||
|
||||
|
||||
class RGBAxes(RGBAxesBase):
|
||||
_defaultAxesClass = Axes
|
||||
@@ -0,0 +1,328 @@
|
||||
|
||||
"""
|
||||
provides a 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.axes import Axes
|
||||
|
||||
|
||||
class _Base(object):
|
||||
"Base class"
|
||||
|
||||
def __rmul__(self, other):
|
||||
float(other) # just to check if number if given
|
||||
return Fraction(other, self)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, _Base):
|
||||
return Add(self, other)
|
||||
else:
|
||||
float(other)
|
||||
other = Fixed(other)
|
||||
return Add(self, 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):
|
||||
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()
|
||||
# when aspec is "auto", consider it as 1.
|
||||
if aspect in ('normal', 'auto'):
|
||||
aspect = 1.
|
||||
elif aspect == "equal":
|
||||
aspect = 1
|
||||
else:
|
||||
aspect = float(aspect)
|
||||
|
||||
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 the largest width (or height) of
|
||||
the given *artist_list*.
|
||||
"""
|
||||
def __init__(self, artist_list, w_or_h):
|
||||
self._artist_list = artist_list
|
||||
|
||||
if w_or_h not in ["width", "height"]:
|
||||
raise ValueError()
|
||||
|
||||
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.
|
||||
w_list, h_list = [], []
|
||||
for a in self._artist_list:
|
||||
bb = a.get_window_extent(renderer)
|
||||
w_list.append(bb.width)
|
||||
h_list.append(bb.height)
|
||||
dpi = a.get_figure().get_dpi()
|
||||
if self._w_or_h == "width":
|
||||
abs_size = max(w_list)/dpi
|
||||
elif self._w_or_h == "height":
|
||||
abs_size = max(h_list)/dpi
|
||||
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class MaxWidth(_Base):
|
||||
"""
|
||||
Size whose absolute part is the largest width of
|
||||
the given *artist_list*.
|
||||
"""
|
||||
def __init__(self, artist_list):
|
||||
self._artist_list = artist_list
|
||||
|
||||
def add_artist(self, a):
|
||||
self._artist_list.append(a)
|
||||
|
||||
def get_size(self, renderer):
|
||||
rel_size = 0.
|
||||
w_list = []
|
||||
for a in self._artist_list:
|
||||
bb = a.get_window_extent(renderer)
|
||||
w_list.append(bb.width)
|
||||
dpi = a.get_figure().get_dpi()
|
||||
abs_size = max(w_list)/dpi
|
||||
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class MaxHeight(_Base):
|
||||
"""
|
||||
Size whose absolute part is the largest height of
|
||||
the given *artist_list*.
|
||||
"""
|
||||
def __init__(self, artist_list):
|
||||
self._artist_list = artist_list
|
||||
|
||||
def add_artist(self, a):
|
||||
self._artist_list.append(a)
|
||||
|
||||
def get_size(self, renderer):
|
||||
rel_size = 0.
|
||||
h_list = []
|
||||
for a in self._artist_list:
|
||||
bb = a.get_window_extent(renderer)
|
||||
h_list.append(bb.height)
|
||||
dpi = a.get_figure().get_dpi()
|
||||
abs_size = max(h_list)/dpi
|
||||
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
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):
|
||||
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):
|
||||
"""
|
||||
Creates 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(object):
|
||||
def _get_left(self, axes_bbox):
|
||||
return axes_bbox.xmin - self.xmin
|
||||
|
||||
def _get_right(self, axes_bbox):
|
||||
return self.xmax - axes_bbox.xmax
|
||||
|
||||
def _get_bottom(self, axes_bbox):
|
||||
return axes_bbox.ymin - self.ymin
|
||||
|
||||
def _get_top(self, axes_bbox):
|
||||
return self.ymax - axes_bbox.ymax
|
||||
|
||||
_get_func_map = dict(left=_get_left,
|
||||
right=_get_right,
|
||||
bottom=_get_bottom,
|
||||
top=_get_top)
|
||||
|
||||
del _get_left, _get_right, _get_bottom, _get_top
|
||||
|
||||
def __init__(self, ax, direction):
|
||||
if isinstance(ax, Axes):
|
||||
self._ax_list = [ax]
|
||||
else:
|
||||
self._ax_list = ax
|
||||
|
||||
try:
|
||||
self._get_func = self._get_func_map[direction]
|
||||
except KeyError:
|
||||
raise KeyError("direction must be one of left, right, bottom, top")
|
||||
|
||||
def __call__(self, renderer):
|
||||
vl = [self._get_func(ax.get_tightbbox(renderer,
|
||||
call_axes_locator=False),
|
||||
ax.bbox)
|
||||
for ax in self._ax_list]
|
||||
return max(vl)
|
||||
@@ -0,0 +1,821 @@
|
||||
"""
|
||||
Colorbar toolkit with two classes and a function:
|
||||
|
||||
:class:`ColorbarBase`
|
||||
the base class with full colorbar drawing functionality.
|
||||
It can be used as-is to make a colorbar for a given colormap;
|
||||
a mappable object (e.g., image) is not needed.
|
||||
|
||||
:class:`Colorbar`
|
||||
the derived class for use with images or contour plots.
|
||||
|
||||
:func:`make_axes`
|
||||
a function for resizing an axes and adding a second axes
|
||||
suitable for a colorbar
|
||||
|
||||
The :meth:`~matplotlib.figure.Figure.colorbar` method uses :func:`make_axes`
|
||||
and :class:`Colorbar`; the :func:`~matplotlib.pyplot.colorbar` function
|
||||
is a thin wrapper over :meth:`~matplotlib.figure.Figure.colorbar`.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
import matplotlib.colors as colors
|
||||
import matplotlib.cm as cm
|
||||
from matplotlib import docstring
|
||||
import matplotlib.ticker as ticker
|
||||
import matplotlib.cbook as cbook
|
||||
import matplotlib.collections as collections
|
||||
import matplotlib.contour as contour
|
||||
from matplotlib.path import Path
|
||||
from matplotlib.patches import PathPatch
|
||||
from matplotlib.transforms import Bbox
|
||||
|
||||
|
||||
make_axes_kw_doc = '''
|
||||
|
||||
============= ====================================================
|
||||
Property Description
|
||||
============= ====================================================
|
||||
*orientation* vertical or horizontal
|
||||
*fraction* 0.15; fraction of original axes to use for colorbar
|
||||
*pad* 0.05 if vertical, 0.15 if horizontal; fraction
|
||||
of original axes between colorbar and new image axes
|
||||
*shrink* 1.0; fraction by which to shrink the colorbar
|
||||
*aspect* 20; ratio of long to short dimensions
|
||||
============= ====================================================
|
||||
|
||||
'''
|
||||
|
||||
colormap_kw_doc = '''
|
||||
|
||||
=========== ====================================================
|
||||
Property Description
|
||||
=========== ====================================================
|
||||
*extend* [ 'neither' | 'both' | 'min' | 'max' ]
|
||||
If not 'neither', make pointed end(s) for out-of-
|
||||
range values. These are set for a given colormap
|
||||
using the colormap set_under and set_over methods.
|
||||
*spacing* [ 'uniform' | 'proportional' ]
|
||||
Uniform spacing gives each discrete color the same
|
||||
space; proportional makes the space proportional to
|
||||
the data interval.
|
||||
*ticks* [ None | list of ticks | Locator object ]
|
||||
If None, ticks are determined automatically from the
|
||||
input.
|
||||
*format* [ None | format string | Formatter object ]
|
||||
If None, the
|
||||
:class:`~matplotlib.ticker.ScalarFormatter` is used.
|
||||
If a format string is given, e.g., '%.3f', that is
|
||||
used. An alternative
|
||||
:class:`~matplotlib.ticker.Formatter` object may be
|
||||
given instead.
|
||||
*drawedges* bool
|
||||
Whether to draw lines at color boundaries.
|
||||
=========== ====================================================
|
||||
|
||||
The following will probably be useful only in the context of
|
||||
indexed colors (that is, when the mappable has norm=NoNorm()),
|
||||
or other unusual circumstances.
|
||||
|
||||
============ ===================================================
|
||||
Property Description
|
||||
============ ===================================================
|
||||
*boundaries* None or a sequence
|
||||
*values* None or a sequence which must be of length 1 less
|
||||
than the sequence of *boundaries*. For each region
|
||||
delimited by adjacent entries in *boundaries*, the
|
||||
color mapped to the corresponding value in values
|
||||
will be used.
|
||||
============ ===================================================
|
||||
|
||||
'''
|
||||
|
||||
colorbar_doc = '''
|
||||
|
||||
Add a colorbar to a plot.
|
||||
|
||||
Function signatures for the :mod:`~matplotlib.pyplot` interface; all
|
||||
but the first are also method signatures for the
|
||||
:meth:`~matplotlib.figure.Figure.colorbar` method::
|
||||
|
||||
colorbar(**kwargs)
|
||||
colorbar(mappable, **kwargs)
|
||||
colorbar(mappable, cax=cax, **kwargs)
|
||||
colorbar(mappable, ax=ax, **kwargs)
|
||||
|
||||
arguments:
|
||||
|
||||
*mappable*
|
||||
the :class:`~matplotlib.image.Image`,
|
||||
:class:`~matplotlib.contour.ContourSet`, etc. to
|
||||
which the colorbar applies; this argument is mandatory for the
|
||||
:meth:`~matplotlib.figure.Figure.colorbar` method but optional for the
|
||||
:func:`~matplotlib.pyplot.colorbar` function, which sets the
|
||||
default to the current image.
|
||||
|
||||
keyword arguments:
|
||||
|
||||
*cax*
|
||||
None | axes object into which the colorbar will be drawn
|
||||
*ax*
|
||||
None | parent axes object from which space for a new
|
||||
colorbar axes will be stolen
|
||||
|
||||
|
||||
Additional keyword arguments are of two kinds:
|
||||
|
||||
axes properties:
|
||||
%s
|
||||
colorbar properties:
|
||||
%s
|
||||
|
||||
If *mappable* is a :class:`~matplotlib.contours.ContourSet`, its *extend*
|
||||
kwarg is included automatically.
|
||||
|
||||
Note that the *shrink* kwarg provides a simple way to keep a vertical
|
||||
colorbar, for example, from being taller than the axes of the mappable
|
||||
to which the colorbar is attached; but it is a manual method requiring
|
||||
some trial and error. If the colorbar is too tall (or a horizontal
|
||||
colorbar is too wide) use a smaller value of *shrink*.
|
||||
|
||||
For more precise control, you can manually specify the positions of
|
||||
the axes objects in which the mappable and the colorbar are drawn. In
|
||||
this case, do not use any of the axes properties kwargs.
|
||||
|
||||
It is known that some vector graphics viewer (svg and pdf) renders white gaps
|
||||
between segments of the colorbar. This is due to bugs in the viewers not
|
||||
matplotlib. As a workaround the colorbar can be rendered with overlapping
|
||||
segments::
|
||||
|
||||
cbar = colorbar()
|
||||
cbar.solids.set_edgecolor("face")
|
||||
draw()
|
||||
|
||||
However this has negative consequences in other circumstances. Particularly with
|
||||
semi transparent images (alpha < 1) and colorbar extensions and is not enabled
|
||||
by default see (issue #1188).
|
||||
|
||||
returns:
|
||||
:class:`~matplotlib.colorbar.Colorbar` instance; see also its base class,
|
||||
:class:`~matplotlib.colorbar.ColorbarBase`. Call the
|
||||
:meth:`~matplotlib.colorbar.ColorbarBase.set_label` method
|
||||
to label the colorbar.
|
||||
|
||||
|
||||
The transData of the *cax* is adjusted so that the limits in the
|
||||
longest axis actually corresponds to the limits in colorbar range. On
|
||||
the other hand, the shortest axis has a data limits of [1,2], whose
|
||||
unconventional value is to prevent underflow when log scale is used.
|
||||
''' % (make_axes_kw_doc, colormap_kw_doc)
|
||||
|
||||
#docstring.interpd.update(colorbar_doc=colorbar_doc)
|
||||
|
||||
|
||||
class CbarAxesLocator(object):
|
||||
"""
|
||||
CbarAxesLocator is a axes_locator for colorbar axes. It adjust the
|
||||
position of the axes to make a room for extended ends, i.e., the
|
||||
extended ends are located outside the axes area.
|
||||
"""
|
||||
|
||||
def __init__(self, locator=None, extend="neither", orientation="vertical"):
|
||||
"""
|
||||
*locator* : the bbox returned from the locator is used as a
|
||||
initial axes location. If None, axes.bbox is used.
|
||||
|
||||
*extend* : same as in ColorbarBase
|
||||
*orientation* : same as in ColorbarBase
|
||||
|
||||
"""
|
||||
self._locator = locator
|
||||
self.extesion_fraction = 0.05
|
||||
self.extend = extend
|
||||
self.orientation = orientation
|
||||
|
||||
def get_original_position(self, axes, renderer):
|
||||
"""
|
||||
get the original position of the axes.
|
||||
"""
|
||||
if self._locator is None:
|
||||
bbox = axes.get_position(original=True)
|
||||
else:
|
||||
bbox = self._locator(axes, renderer)
|
||||
return bbox
|
||||
|
||||
def get_end_vertices(self):
|
||||
"""
|
||||
return a tuple of two vertices for the colorbar extended ends.
|
||||
The first vertices is for the minimum end, and the second is for
|
||||
the maximum end.
|
||||
"""
|
||||
# Note that concatenating two vertices needs to make a
|
||||
# vertices for the frame.
|
||||
extesion_fraction = self.extesion_fraction
|
||||
|
||||
corx = extesion_fraction*2.
|
||||
cory = 1./(1. - corx)
|
||||
x1, y1, w, h = 0, 0, 1, 1
|
||||
x2, y2 = x1 + w, y1 + h
|
||||
dw, dh = w*extesion_fraction, h*extesion_fraction*cory
|
||||
|
||||
if self.extend in ["min", "both"]:
|
||||
bottom = [(x1, y1),
|
||||
(x1+w/2., y1-dh),
|
||||
(x2, y1)]
|
||||
else:
|
||||
bottom = [(x1, y1),
|
||||
(x2, y1)]
|
||||
|
||||
if self.extend in ["max", "both"]:
|
||||
top = [(x2, y2),
|
||||
(x1+w/2., y2+dh),
|
||||
(x1, y2)]
|
||||
else:
|
||||
top = [(x2, y2),
|
||||
(x1, y2)]
|
||||
|
||||
if self.orientation == "horizontal":
|
||||
bottom = [(y,x) for (x,y) in bottom]
|
||||
top = [(y,x) for (x,y) in top]
|
||||
|
||||
return bottom, top
|
||||
|
||||
|
||||
def get_path_patch(self):
|
||||
"""
|
||||
get the path for axes patch
|
||||
"""
|
||||
end1, end2 = self.get_end_vertices()
|
||||
|
||||
verts = [] + end1 + end2 + end1[:1]
|
||||
|
||||
return Path(verts)
|
||||
|
||||
|
||||
def get_path_ends(self):
|
||||
"""
|
||||
get the paths for extended ends
|
||||
"""
|
||||
|
||||
end1, end2 = self.get_end_vertices()
|
||||
|
||||
return Path(end1), Path(end2)
|
||||
|
||||
|
||||
def __call__(self, axes, renderer):
|
||||
"""
|
||||
Return the adjusted position of the axes
|
||||
"""
|
||||
bbox0 = self.get_original_position(axes, renderer)
|
||||
bbox = bbox0
|
||||
|
||||
x1, y1, w, h = bbox.bounds
|
||||
extesion_fraction = self.extesion_fraction
|
||||
dw, dh = w*extesion_fraction, h*extesion_fraction
|
||||
|
||||
if self.extend in ["min", "both"]:
|
||||
if self.orientation == "horizontal":
|
||||
x1 = x1 + dw
|
||||
else:
|
||||
y1 = y1+dh
|
||||
|
||||
if self.extend in ["max", "both"]:
|
||||
if self.orientation == "horizontal":
|
||||
w = w-2*dw
|
||||
else:
|
||||
h = h-2*dh
|
||||
|
||||
return Bbox.from_bounds(x1, y1, w, h)
|
||||
|
||||
|
||||
|
||||
class ColorbarBase(cm.ScalarMappable):
|
||||
'''
|
||||
Draw a colorbar in an existing axes.
|
||||
|
||||
This is a base class for the :class:`Colorbar` class, which is the
|
||||
basis for the :func:`~matplotlib.pyplot.colorbar` method and pyplot
|
||||
function.
|
||||
|
||||
It is also useful by itself for showing a colormap. If the *cmap*
|
||||
kwarg is given but *boundaries* and *values* are left as None,
|
||||
then the colormap will be displayed on a 0-1 scale. To show the
|
||||
under- and over-value colors, specify the *norm* as::
|
||||
|
||||
colors.Normalize(clip=False)
|
||||
|
||||
To show the colors versus index instead of on the 0-1 scale,
|
||||
use::
|
||||
|
||||
norm=colors.NoNorm.
|
||||
|
||||
Useful attributes:
|
||||
|
||||
:attr:`ax`
|
||||
the Axes instance in which the colorbar is drawn
|
||||
|
||||
:attr:`lines`
|
||||
a LineCollection if lines were drawn, otherwise None
|
||||
|
||||
:attr:`dividers`
|
||||
a LineCollection if *drawedges* is True, otherwise None
|
||||
|
||||
Useful public methods are :meth:`set_label` and :meth:`add_lines`.
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, ax, cmap=None,
|
||||
norm=None,
|
||||
alpha=1.0,
|
||||
values=None,
|
||||
boundaries=None,
|
||||
orientation='vertical',
|
||||
extend='neither',
|
||||
spacing='uniform', # uniform or proportional
|
||||
ticks=None,
|
||||
format=None,
|
||||
drawedges=False,
|
||||
filled=True,
|
||||
):
|
||||
self.ax = ax
|
||||
|
||||
if cmap is None: cmap = cm.get_cmap()
|
||||
if norm is None: norm = colors.Normalize()
|
||||
self.alpha = alpha
|
||||
cm.ScalarMappable.__init__(self, cmap=cmap, norm=norm)
|
||||
self.values = values
|
||||
self.boundaries = boundaries
|
||||
self.extend = extend
|
||||
self.spacing = spacing
|
||||
self.orientation = orientation
|
||||
self.drawedges = drawedges
|
||||
self.filled = filled
|
||||
|
||||
# artists
|
||||
self.solids = None
|
||||
self.lines = None
|
||||
self.dividers = None
|
||||
self.extension_patch1 = None
|
||||
self.extension_patch2 = None
|
||||
|
||||
if orientation == "vertical":
|
||||
self.cbar_axis = self.ax.yaxis
|
||||
else:
|
||||
self.cbar_axis = self.ax.xaxis
|
||||
|
||||
|
||||
if format is None:
|
||||
if isinstance(self.norm, colors.LogNorm):
|
||||
# change both axis for proper aspect
|
||||
self.ax.set_xscale("log")
|
||||
self.ax.set_yscale("log")
|
||||
self.cbar_axis.set_minor_locator(ticker.NullLocator())
|
||||
formatter = ticker.LogFormatter()
|
||||
else:
|
||||
formatter = None
|
||||
elif isinstance(format, str):
|
||||
formatter = ticker.FormatStrFormatter(format)
|
||||
else:
|
||||
formatter = format # Assume it is a Formatter
|
||||
|
||||
if formatter is None:
|
||||
formatter = self.cbar_axis.get_major_formatter()
|
||||
else:
|
||||
self.cbar_axis.set_major_formatter(formatter)
|
||||
|
||||
if cbook.iterable(ticks):
|
||||
self.cbar_axis.set_ticks(ticks)
|
||||
elif ticks is not None:
|
||||
self.cbar_axis.set_major_locator(ticks)
|
||||
else:
|
||||
self._select_locator(formatter)
|
||||
|
||||
|
||||
self._config_axes()
|
||||
|
||||
self.update_artists()
|
||||
|
||||
self.set_label_text('')
|
||||
|
||||
|
||||
def _get_colorbar_limits(self):
|
||||
"""
|
||||
initial limits for colorbar range. The returned min, max values
|
||||
will be used to create colorbar solid(?) and etc.
|
||||
"""
|
||||
if self.boundaries is not None:
|
||||
C = self.boundaries
|
||||
if self.extend in ["min", "both"]:
|
||||
C = C[1:]
|
||||
|
||||
if self.extend in ["max", "both"]:
|
||||
C = C[:-1]
|
||||
return min(C), max(C)
|
||||
else:
|
||||
return self.get_clim()
|
||||
|
||||
|
||||
def _config_axes(self):
|
||||
'''
|
||||
Adjust the properties of the axes to be adequate for colorbar display.
|
||||
'''
|
||||
ax = self.ax
|
||||
|
||||
axes_locator = CbarAxesLocator(ax.get_axes_locator(),
|
||||
extend=self.extend,
|
||||
orientation=self.orientation)
|
||||
ax.set_axes_locator(axes_locator)
|
||||
|
||||
# override the get_data_ratio for the aspect works.
|
||||
def _f():
|
||||
return 1.
|
||||
ax.get_data_ratio = _f
|
||||
ax.get_data_ratio_log = _f
|
||||
|
||||
ax.set_frame_on(True)
|
||||
ax.set_navigate(False)
|
||||
|
||||
self.ax.set_autoscalex_on(False)
|
||||
self.ax.set_autoscaley_on(False)
|
||||
|
||||
if self.orientation == 'horizontal':
|
||||
ax.xaxis.set_label_position('bottom')
|
||||
ax.set_yticks([])
|
||||
else:
|
||||
ax.set_xticks([])
|
||||
ax.yaxis.set_label_position('right')
|
||||
ax.yaxis.set_ticks_position('right')
|
||||
|
||||
|
||||
|
||||
def update_artists(self):
|
||||
"""
|
||||
Update the colorbar associated artists, *filled* and
|
||||
*ends*. Note that *lines* are not updated. This needs to be
|
||||
called whenever clim of associated image changes.
|
||||
"""
|
||||
self._process_values()
|
||||
self._add_ends()
|
||||
|
||||
X, Y = self._mesh()
|
||||
if self.filled:
|
||||
C = self._values[:,np.newaxis]
|
||||
self._add_solids(X, Y, C)
|
||||
|
||||
ax = self.ax
|
||||
vmin, vmax = self._get_colorbar_limits()
|
||||
if self.orientation == 'horizontal':
|
||||
ax.set_ylim(1, 2)
|
||||
ax.set_xlim(vmin, vmax)
|
||||
else:
|
||||
ax.set_xlim(1, 2)
|
||||
ax.set_ylim(vmin, vmax)
|
||||
|
||||
|
||||
def _add_ends(self):
|
||||
"""
|
||||
Create patches from extended ends and add them to the axes.
|
||||
"""
|
||||
|
||||
del self.extension_patch1
|
||||
del self.extension_patch2
|
||||
|
||||
path1, path2 = self.ax.get_axes_locator().get_path_ends()
|
||||
fc=mpl.rcParams['axes.facecolor']
|
||||
ec=mpl.rcParams['axes.edgecolor']
|
||||
linewidths=0.5*mpl.rcParams['axes.linewidth']
|
||||
self.extension_patch1 = PathPatch(path1,
|
||||
fc=fc, ec=ec, lw=linewidths,
|
||||
zorder=2.,
|
||||
transform=self.ax.transAxes,
|
||||
clip_on=False)
|
||||
self.extension_patch2 = PathPatch(path2,
|
||||
fc=fc, ec=ec, lw=linewidths,
|
||||
zorder=2.,
|
||||
transform=self.ax.transAxes,
|
||||
clip_on=False)
|
||||
self.ax.add_artist(self.extension_patch1)
|
||||
self.ax.add_artist(self.extension_patch2)
|
||||
|
||||
|
||||
|
||||
def _set_label_text(self):
|
||||
"""
|
||||
set label.
|
||||
"""
|
||||
self.cbar_axis.set_label_text(self._label, **self._labelkw)
|
||||
|
||||
def set_label_text(self, label, **kw):
|
||||
'''
|
||||
Label the long axis of the colorbar
|
||||
'''
|
||||
self._label = label
|
||||
self._labelkw = kw
|
||||
self._set_label_text()
|
||||
|
||||
|
||||
def _edges(self, X, Y):
|
||||
'''
|
||||
Return the separator line segments; helper for _add_solids.
|
||||
'''
|
||||
N = X.shape[0]
|
||||
# Using the non-array form of these line segments is much
|
||||
# simpler than making them into arrays.
|
||||
if self.orientation == 'vertical':
|
||||
return [list(zip(X[i], Y[i])) for i in range(1, N-1)]
|
||||
else:
|
||||
return [list(zip(Y[i], X[i])) for i in range(1, N-1)]
|
||||
|
||||
def _add_solids(self, X, Y, C):
|
||||
'''
|
||||
Draw the colors using :meth:`~matplotlib.axes.Axes.pcolormesh`;
|
||||
optionally add separators.
|
||||
'''
|
||||
## Change to pcolorfast after fixing bugs in some backends...
|
||||
|
||||
if self.extend in ["min", "both"]:
|
||||
cc = self.to_rgba([C[0][0]])
|
||||
self.extension_patch1.set_fc(cc[0])
|
||||
X, Y, C = X[1:], Y[1:], C[1:]
|
||||
|
||||
if self.extend in ["max", "both"]:
|
||||
cc = self.to_rgba([C[-1][0]])
|
||||
self.extension_patch2.set_fc(cc[0])
|
||||
X, Y, C = X[:-1], Y[:-1], C[:-1]
|
||||
|
||||
if self.orientation == 'vertical':
|
||||
args = (X, Y, C)
|
||||
else:
|
||||
args = (np.transpose(Y), np.transpose(X), np.transpose(C))
|
||||
kw = {'cmap':self.cmap, 'norm':self.norm,
|
||||
'shading':'flat', 'alpha':self.alpha,
|
||||
}
|
||||
|
||||
del self.solids
|
||||
del self.dividers
|
||||
|
||||
col = self.ax.pcolormesh(*args, **kw)
|
||||
|
||||
self.solids = col
|
||||
if self.drawedges:
|
||||
self.dividers = collections.LineCollection(
|
||||
self._edges(X,Y),
|
||||
colors=(mpl.rcParams['axes.edgecolor'],),
|
||||
linewidths=(0.5*mpl.rcParams['axes.linewidth'],),
|
||||
)
|
||||
self.ax.add_collection(self.dividers)
|
||||
else:
|
||||
self.dividers = None
|
||||
|
||||
def add_lines(self, levels, colors, linewidths):
|
||||
'''
|
||||
Draw lines on the colorbar. It deletes preexisting lines.
|
||||
'''
|
||||
X, Y = np.meshgrid([1, 2], levels)
|
||||
if self.orientation == 'vertical':
|
||||
xy = np.stack([X, Y], axis=-1)
|
||||
else:
|
||||
xy = np.stack([Y, X], axis=-1)
|
||||
col = collections.LineCollection(xy, linewidths=linewidths)
|
||||
self.lines = col
|
||||
col.set_color(colors)
|
||||
self.ax.add_collection(col)
|
||||
|
||||
def _select_locator(self, formatter):
|
||||
'''
|
||||
select a suitable locator
|
||||
'''
|
||||
if self.boundaries is None:
|
||||
if isinstance(self.norm, colors.NoNorm):
|
||||
nv = len(self._values)
|
||||
base = 1 + int(nv/10)
|
||||
locator = ticker.IndexLocator(base=base, offset=0)
|
||||
elif isinstance(self.norm, colors.BoundaryNorm):
|
||||
b = self.norm.boundaries
|
||||
locator = ticker.FixedLocator(b, nbins=10)
|
||||
elif isinstance(self.norm, colors.LogNorm):
|
||||
locator = ticker.LogLocator()
|
||||
else:
|
||||
locator = ticker.MaxNLocator(nbins=5)
|
||||
else:
|
||||
b = self._boundaries[self._inside]
|
||||
locator = ticker.FixedLocator(b) #, nbins=10)
|
||||
|
||||
self.cbar_axis.set_major_locator(locator)
|
||||
|
||||
|
||||
def _process_values(self, b=None):
|
||||
'''
|
||||
Set the :attr:`_boundaries` and :attr:`_values` attributes
|
||||
based on the input boundaries and values. Input boundaries
|
||||
can be *self.boundaries* or the argument *b*.
|
||||
'''
|
||||
if b is None:
|
||||
b = self.boundaries
|
||||
if b is not None:
|
||||
self._boundaries = np.asarray(b, dtype=float)
|
||||
if self.values is None:
|
||||
self._values = 0.5*(self._boundaries[:-1]
|
||||
+ self._boundaries[1:])
|
||||
if isinstance(self.norm, colors.NoNorm):
|
||||
self._values = (self._values + 0.00001).astype(np.int16)
|
||||
return
|
||||
self._values = np.array(self.values)
|
||||
return
|
||||
if self.values is not None:
|
||||
self._values = np.array(self.values)
|
||||
if self.boundaries is None:
|
||||
b = np.zeros(len(self.values)+1, 'd')
|
||||
b[1:-1] = 0.5*(self._values[:-1] - self._values[1:])
|
||||
b[0] = 2.0*b[1] - b[2]
|
||||
b[-1] = 2.0*b[-2] - b[-3]
|
||||
self._boundaries = b
|
||||
return
|
||||
self._boundaries = np.array(self.boundaries)
|
||||
return
|
||||
# Neither boundaries nor values are specified;
|
||||
# make reasonable ones based on cmap and norm.
|
||||
if isinstance(self.norm, colors.NoNorm):
|
||||
b = self._uniform_y(self.cmap.N+1) * self.cmap.N - 0.5
|
||||
v = np.zeros((len(b)-1,), dtype=np.int16)
|
||||
v = np.arange(self.cmap.N, dtype=np.int16)
|
||||
self._boundaries = b
|
||||
self._values = v
|
||||
return
|
||||
elif isinstance(self.norm, colors.BoundaryNorm):
|
||||
b = np.array(self.norm.boundaries)
|
||||
v = np.zeros((len(b)-1,), dtype=float)
|
||||
bi = self.norm.boundaries
|
||||
v = 0.5*(bi[:-1] + bi[1:])
|
||||
self._boundaries = b
|
||||
self._values = v
|
||||
return
|
||||
else:
|
||||
b = self._uniform_y(self.cmap.N+1)
|
||||
|
||||
self._process_values(b)
|
||||
|
||||
|
||||
def _uniform_y(self, N):
|
||||
'''
|
||||
Return colorbar data coordinates for *N* uniformly
|
||||
spaced boundaries.
|
||||
'''
|
||||
vmin, vmax = self._get_colorbar_limits()
|
||||
if isinstance(self.norm, colors.LogNorm):
|
||||
y = np.logspace(np.log10(vmin), np.log10(vmax), N)
|
||||
else:
|
||||
y = np.linspace(vmin, vmax, N)
|
||||
return y
|
||||
|
||||
def _mesh(self):
|
||||
'''
|
||||
Return X,Y, the coordinate arrays for the colorbar pcolormesh.
|
||||
These are suitable for a vertical colorbar; swapping and
|
||||
transposition for a horizontal colorbar are done outside
|
||||
this function.
|
||||
'''
|
||||
x = np.array([1.0, 2.0])
|
||||
if self.spacing == 'uniform':
|
||||
y = self._uniform_y(len(self._boundaries))
|
||||
else:
|
||||
y = self._boundaries
|
||||
self._y = y
|
||||
|
||||
X, Y = np.meshgrid(x,y)
|
||||
return X, Y
|
||||
|
||||
|
||||
def set_alpha(self, alpha):
|
||||
"""
|
||||
set alpha value.
|
||||
"""
|
||||
self.alpha = alpha
|
||||
|
||||
|
||||
class Colorbar(ColorbarBase):
|
||||
def __init__(self, ax, mappable, **kw):
|
||||
mappable.autoscale_None() # Ensure mappable.norm.vmin, vmax
|
||||
# are set when colorbar is called,
|
||||
# even if mappable.draw has not yet
|
||||
# been called. This will not change
|
||||
# vmin, vmax if they are already set.
|
||||
self.mappable = mappable
|
||||
kw['cmap'] = mappable.cmap
|
||||
kw['norm'] = mappable.norm
|
||||
kw['alpha'] = mappable.get_alpha()
|
||||
if isinstance(mappable, contour.ContourSet):
|
||||
CS = mappable
|
||||
kw['boundaries'] = CS._levels
|
||||
kw['values'] = CS.cvalues
|
||||
kw['extend'] = CS.extend
|
||||
#kw['ticks'] = CS._levels
|
||||
kw.setdefault('ticks', ticker.FixedLocator(CS.levels, nbins=10))
|
||||
kw['filled'] = CS.filled
|
||||
ColorbarBase.__init__(self, ax, **kw)
|
||||
if not CS.filled:
|
||||
self.add_lines(CS)
|
||||
else:
|
||||
ColorbarBase.__init__(self, ax, **kw)
|
||||
|
||||
|
||||
def add_lines(self, CS):
|
||||
'''
|
||||
Add the lines from a non-filled
|
||||
:class:`~matplotlib.contour.ContourSet` to the colorbar.
|
||||
'''
|
||||
if not isinstance(CS, contour.ContourSet) or CS.filled:
|
||||
raise ValueError('add_lines is only for a ContourSet of lines')
|
||||
tcolors = [c[0] for c in CS.tcolors]
|
||||
tlinewidths = [t[0] for t in CS.tlinewidths]
|
||||
# The following was an attempt to get the colorbar lines
|
||||
# to follow subsequent changes in the contour lines,
|
||||
# but more work is needed: specifically, a careful
|
||||
# look at event sequences, and at how
|
||||
# to make one object track another automatically.
|
||||
#tcolors = [col.get_colors()[0] for col in CS.collections]
|
||||
#tlinewidths = [col.get_linewidth()[0] for lw in CS.collections]
|
||||
ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths)
|
||||
|
||||
def update_bruteforce(self, mappable):
|
||||
"""
|
||||
Update the colorbar artists to reflect the change of the
|
||||
associated mappable.
|
||||
"""
|
||||
self.update_artists()
|
||||
|
||||
if isinstance(mappable, contour.ContourSet):
|
||||
if not mappable.filled:
|
||||
self.add_lines(mappable)
|
||||
|
||||
@docstring.Substitution(make_axes_kw_doc)
|
||||
def make_axes(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
|
||||
'''
|
||||
Resize and reposition a parent axes, and return a child
|
||||
axes suitable for a colorbar
|
||||
|
||||
::
|
||||
|
||||
cax, kw = make_axes(parent, **kw)
|
||||
|
||||
Keyword arguments may include the following (with defaults):
|
||||
|
||||
*orientation*
|
||||
'vertical' or 'horizontal'
|
||||
|
||||
%s
|
||||
|
||||
All but the first of these are stripped from the input kw set.
|
||||
|
||||
Returns (cax, kw), the child axes and the reduced kw dictionary.
|
||||
'''
|
||||
orientation = kw.setdefault('orientation', 'vertical')
|
||||
#pb = transforms.PBox(parent.get_position())
|
||||
pb = parent.get_position(original=True).frozen()
|
||||
if orientation == 'vertical':
|
||||
pad = kw.pop('pad', 0.05)
|
||||
x1 = 1.0-fraction
|
||||
pb1, pbx, pbcb = pb.splitx(x1-pad, x1)
|
||||
pbcb = pbcb.shrunk(1.0, shrink).anchored('C', pbcb)
|
||||
anchor = (0.0, 0.5)
|
||||
panchor = (1.0, 0.5)
|
||||
else:
|
||||
pad = kw.pop('pad', 0.15)
|
||||
pbcb, pbx, pb1 = pb.splity(fraction, fraction+pad)
|
||||
pbcb = pbcb.shrunk(shrink, 1.0).anchored('C', pbcb)
|
||||
aspect = 1.0/aspect
|
||||
anchor = (0.5, 1.0)
|
||||
panchor = (0.5, 0.0)
|
||||
parent.set_position(pb1)
|
||||
parent.set_anchor(panchor)
|
||||
fig = parent.get_figure()
|
||||
cax = fig.add_axes(pbcb)
|
||||
cax.set_aspect(aspect, anchor=anchor, adjustable='box')
|
||||
return cax, kw
|
||||
|
||||
@docstring.Substitution(colorbar_doc)
|
||||
def colorbar(mappable, cax=None, ax=None, **kw):
|
||||
"""
|
||||
Create a colorbar for a ScalarMappable instance.
|
||||
|
||||
Documentation for the pyplot thin wrapper:
|
||||
|
||||
%s
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
if ax is None:
|
||||
ax = plt.gca()
|
||||
if cax is None:
|
||||
cax, kw = make_axes(ax, **kw)
|
||||
cb = Colorbar(cax, mappable, **kw)
|
||||
|
||||
def on_changed(m):
|
||||
cb.set_cmap(m.get_cmap())
|
||||
cb.set_clim(m.get_clim())
|
||||
cb.update_bruteforce(m)
|
||||
|
||||
cbid = mappable.callbacksSM.connect('changed', on_changed)
|
||||
mappable.colorbar = cb
|
||||
ax.figure.sca(ax)
|
||||
return cb
|
||||
@@ -0,0 +1,680 @@
|
||||
"""
|
||||
A collection of functions and objects for creating or placing inset axes.
|
||||
"""
|
||||
|
||||
import warnings
|
||||
from matplotlib import 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(object):
|
||||
@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):
|
||||
x, y, w, h = self.get_bbox_to_anchor().bounds
|
||||
|
||||
dpi = renderer.points_to_pixels(72.)
|
||||
|
||||
r, a = self.x_size.get_size(renderer)
|
||||
width = w * r + a * dpi
|
||||
|
||||
r, a = self.y_size.get_size(renderer)
|
||||
height = h * 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)
|
||||
|
||||
x, y, w, h = bb.bounds
|
||||
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
|
||||
pad = self.pad * fontsize
|
||||
|
||||
return abs(w * self.zoom) + 2 * pad, abs(h * 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)s
|
||||
"""
|
||||
if "transform" in kwargs:
|
||||
raise ValueError("transform should not be set")
|
||||
|
||||
kwargs["transform"] = IdentityTransform()
|
||||
Patch.__init__(self, **kwargs)
|
||||
self.bbox = bbox
|
||||
|
||||
def get_path(self):
|
||||
x0, y0, x1, y1 = self.bbox.extents
|
||||
|
||||
verts = [(x0, y0),
|
||||
(x1, y0),
|
||||
(x1, y1),
|
||||
(x0, y1),
|
||||
(x0, y0),
|
||||
(0, 0)]
|
||||
|
||||
codes = [Path.MOVETO,
|
||||
Path.LINETO,
|
||||
Path.LINETO,
|
||||
Path.LINETO,
|
||||
Path.LINETO,
|
||||
Path.CLOSEPOLY]
|
||||
|
||||
return Path(verts, codes)
|
||||
|
||||
get_path.__doc__ = Patch.get_path.__doc__
|
||||
|
||||
|
||||
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):
|
||||
transform = bbox1.get_transform()
|
||||
bbox1 = Bbox.from_bounds(0, 0, 1, 1)
|
||||
bbox1 = TransformedBbox(bbox1, transform)
|
||||
|
||||
if isinstance(bbox2, Rectangle):
|
||||
transform = bbox2.get_transform()
|
||||
bbox2 = Bbox.from_bounds(0, 0, 1, 1)
|
||||
bbox2 = TransformedBbox(bbox2, 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)
|
||||
|
||||
verts = [[x1, y1], [x2, y2]]
|
||||
codes = [Path.MOVETO, Path.LINETO]
|
||||
|
||||
return Path(verts, codes)
|
||||
|
||||
@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)s
|
||||
"""
|
||||
if "transform" in kwargs:
|
||||
raise ValueError("transform should not be set")
|
||||
|
||||
kwargs["transform"] = IdentityTransform()
|
||||
if 'fill' in kwargs:
|
||||
Patch.__init__(self, **kwargs)
|
||||
else:
|
||||
fill = ('fc' in kwargs) or ('facecolor' in kwargs) or ('color' in kwargs)
|
||||
Patch.__init__(self, fill=fill, **kwargs)
|
||||
self.bbox1 = bbox1
|
||||
self.bbox2 = bbox2
|
||||
self.loc1 = loc1
|
||||
self.loc2 = loc2
|
||||
|
||||
def get_path(self):
|
||||
return self.connect_bbox(self.bbox1, self.bbox2,
|
||||
self.loc1, self.loc2)
|
||||
|
||||
get_path.__doc__ = Patch.get_path.__doc__
|
||||
|
||||
|
||||
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)s
|
||||
"""
|
||||
if "transform" in kwargs:
|
||||
raise ValueError("transform should not be set")
|
||||
BboxConnector.__init__(self, bbox1, bbox2, loc1a, loc2a, **kwargs)
|
||||
self.loc1b = loc1b
|
||||
self.loc2b = loc2b
|
||||
|
||||
def get_path(self):
|
||||
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)
|
||||
|
||||
get_path.__doc__ = BboxConnector.get_path.__doc__
|
||||
|
||||
|
||||
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
|
||||
:ref:`the examples <sphx_glr_gallery_axes_grid1_inset_locator_demo.py>`.
|
||||
|
||||
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 string, optional, default to 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)s
|
||||
|
||||
borderpad : float, optional
|
||||
Padding between inset axes and the bbox_to_anchor. Defaults to 0.5.
|
||||
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:
|
||||
warnings.warn("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
|
||||
:ref:`the examples <sphx_glr_gallery_axes_grid1_inset_locator_demo2.py>`.
|
||||
|
||||
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 string, optional, default to 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,
|
||||
*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)s
|
||||
|
||||
borderpad : float, optional
|
||||
Padding between inset axes and the bbox_to_anchor. Defaults to 0.5.
|
||||
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)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 = ('fc' in kwargs) or ('facecolor' in kwargs) or ('color' in 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,144 @@
|
||||
import matplotlib.axes as maxes
|
||||
from matplotlib.artist import Artist
|
||||
from matplotlib.axis import XAxis, YAxis
|
||||
|
||||
|
||||
class SimpleChainedObjects(object):
|
||||
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, *kl, **kwargs):
|
||||
for m in self._objects:
|
||||
m(*kl, **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(Axes.AxisDict, self).__getitem__(k1) for k1 in k])
|
||||
# super() within a list comprehension needs explicit args
|
||||
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"]))
|
||||
|
||||
def _get_axislines(self):
|
||||
return self._axislines
|
||||
|
||||
axis = property(_get_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("axis must be instance of XAxis or YAxis : %s is provided" % (axis,))
|
||||
Artist.__init__(self)
|
||||
|
||||
|
||||
def _get_major_ticks(self):
|
||||
tickline = "tick%dline" % self._axisnum
|
||||
return SimpleChainedObjects([getattr(tick, tickline)
|
||||
for tick in self._axis.get_major_ticks()])
|
||||
|
||||
def _get_major_ticklabels(self):
|
||||
label = "label%d" % self._axisnum
|
||||
return SimpleChainedObjects([getattr(tick, label)
|
||||
for tick in self._axis.get_major_ticks()])
|
||||
|
||||
def _get_label(self):
|
||||
return self._axis.label
|
||||
|
||||
major_ticks = property(_get_major_ticks)
|
||||
major_ticklabels = property(_get_major_ticklabels)
|
||||
label = property(_get_label)
|
||||
|
||||
def set_visible(self, b):
|
||||
self.toggle(all=b)
|
||||
self.line.set_visible(b)
|
||||
self._axis.set_visible(True)
|
||||
Artist.set_visible(self, 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)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import matplotlib.pyplot as plt
|
||||
fig = plt.figure()
|
||||
ax = Axes(fig, [0.1, 0.1, 0.8, 0.8])
|
||||
fig.add_axes(ax)
|
||||
ax.cla()
|
||||
@@ -0,0 +1,407 @@
|
||||
import functools
|
||||
|
||||
from matplotlib import (
|
||||
artist as martist, collections as mcoll, transforms as mtransforms,
|
||||
rcParams)
|
||||
from matplotlib.axes import subplot_class_factory
|
||||
from matplotlib.transforms import Bbox
|
||||
from .mpl_axes import Axes
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
class ParasiteAxesBase:
|
||||
|
||||
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 __init__(self, parent_axes, **kwargs):
|
||||
self._parent_axes = parent_axes
|
||||
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
|
||||
|
||||
# In mpl's Axes, zorders of x- and y-axis are originally set
|
||||
# within Axes.draw().
|
||||
if self._axisbelow:
|
||||
self.xaxis.set_zorder(0.5)
|
||||
self.yaxis.set_zorder(0.5)
|
||||
else:
|
||||
self.xaxis.set_zorder(2.5)
|
||||
self.yaxis.set_zorder(2.5)
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def parasite_axes_class_factory(axes_class=None):
|
||||
if axes_class is None:
|
||||
axes_class = Axes
|
||||
|
||||
return type("%sParasite" % axes_class.__name__,
|
||||
(ParasiteAxesBase, axes_class), {})
|
||||
|
||||
|
||||
ParasiteAxes = parasite_axes_class_factory()
|
||||
|
||||
|
||||
class ParasiteAxesAuxTransBase:
|
||||
def __init__(self, parent_axes, aux_transform, viewlim_mode=None,
|
||||
**kwargs):
|
||||
self.transAux = aux_transform
|
||||
self.set_viewlim_mode(viewlim_mode)
|
||||
super().__init__(parent_axes, **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):
|
||||
if mode not in [None, "equal", "transform"]:
|
||||
raise ValueError("Unknown mode: %s" % (mode,))
|
||||
else:
|
||||
self._viewlim_mode = mode
|
||||
|
||||
def get_viewlim_mode(self):
|
||||
return self._viewlim_mode
|
||||
|
||||
def update_viewlim(self):
|
||||
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:
|
||||
raise ValueError("Unknown mode: %s" % (self._viewlim_mode,))
|
||||
|
||||
def _pcolor(self, super_pcolor, *XYC, **kwargs):
|
||||
if len(XYC) == 1:
|
||||
C = XYC[0]
|
||||
ny, nx = C.shape
|
||||
|
||||
gx = np.arange(-0.5, nx)
|
||||
gy = np.arange(-0.5, ny)
|
||||
|
||||
X, Y = np.meshgrid(gx, gy)
|
||||
else:
|
||||
X, Y, C = XYC
|
||||
|
||||
if "transform" in kwargs:
|
||||
mesh = super_pcolor(X, Y, C, **kwargs)
|
||||
else:
|
||||
orig_shape = X.shape
|
||||
xyt = np.column_stack([X.flat, Y.flat])
|
||||
wxy = self.transAux.transform(xyt)
|
||||
gx = wxy[:, 0].reshape(orig_shape)
|
||||
gy = wxy[:, 1].reshape(orig_shape)
|
||||
mesh = super_pcolor(gx, gy, C, **kwargs)
|
||||
mesh.set_transform(self._parent_axes.transData)
|
||||
|
||||
return mesh
|
||||
|
||||
def pcolormesh(self, *XYC, **kwargs):
|
||||
return self._pcolor(super().pcolormesh, *XYC, **kwargs)
|
||||
|
||||
def pcolor(self, *XYC, **kwargs):
|
||||
return self._pcolor(super().pcolor, *XYC, **kwargs)
|
||||
|
||||
def _contour(self, super_contour, *XYCL, **kwargs):
|
||||
|
||||
if len(XYCL) <= 2:
|
||||
C = XYCL[0]
|
||||
ny, nx = C.shape
|
||||
|
||||
gx = np.arange(0., nx)
|
||||
gy = np.arange(0., ny)
|
||||
|
||||
X, Y = np.meshgrid(gx, gy)
|
||||
CL = XYCL
|
||||
else:
|
||||
X, Y = XYCL[:2]
|
||||
CL = XYCL[2:]
|
||||
|
||||
if "transform" in kwargs:
|
||||
cont = super_contour(X, Y, *CL, **kwargs)
|
||||
else:
|
||||
orig_shape = X.shape
|
||||
xyt = np.column_stack([X.flat, Y.flat])
|
||||
wxy = self.transAux.transform(xyt)
|
||||
gx = wxy[:, 0].reshape(orig_shape)
|
||||
gy = wxy[:, 1].reshape(orig_shape)
|
||||
cont = super_contour(gx, gy, *CL, **kwargs)
|
||||
for c in cont.collections:
|
||||
c.set_transform(self._parent_axes.transData)
|
||||
|
||||
return cont
|
||||
|
||||
def contour(self, *XYCL, **kwargs):
|
||||
return self._contour(super().contour, *XYCL, **kwargs)
|
||||
|
||||
def contourf(self, *XYCL, **kwargs):
|
||||
return self._contour(super().contourf, *XYCL, **kwargs)
|
||||
|
||||
def apply_aspect(self, position=None):
|
||||
self.update_viewlim()
|
||||
super().apply_aspect()
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def parasite_axes_auxtrans_class_factory(axes_class=None):
|
||||
if axes_class is None:
|
||||
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'})
|
||||
|
||||
|
||||
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(
|
||||
axes_class=ParasiteAxes)
|
||||
|
||||
|
||||
class HostAxesBase:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.parasites = []
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_aux_axes(self, tr, viewlim_mode="equal", axes_class=None):
|
||||
parasite_axes_class = parasite_axes_auxtrans_class_factory(axes_class)
|
||||
ax2 = parasite_axes_class(self, tr, viewlim_mode)
|
||||
# note that ax2.transData == tr + ax1.transData
|
||||
# Anthing 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 twinx(self, axes_class=None):
|
||||
"""
|
||||
create a twin of Axes for generating a plot with a sharex
|
||||
x-axis but independent y axis. The y-axis of self will have
|
||||
ticks on left and the returned axes will have ticks on the
|
||||
right
|
||||
"""
|
||||
|
||||
if axes_class is None:
|
||||
axes_class = self._get_base_axes()
|
||||
|
||||
parasite_axes_class = parasite_axes_class_factory(axes_class)
|
||||
|
||||
ax2 = parasite_axes_class(self, sharex=self, frameon=False)
|
||||
self.parasites.append(ax2)
|
||||
ax2._remove_method = self._remove_twinx
|
||||
|
||||
self.axis["right"].set_visible(False)
|
||||
|
||||
ax2.axis["right"].set_visible(True)
|
||||
ax2.axis["left", "top", "bottom"].set_visible(False)
|
||||
|
||||
return ax2
|
||||
|
||||
def _remove_twinx(self, ax):
|
||||
self.parasites.remove(ax)
|
||||
self.axis["right"].set_visible(True)
|
||||
self.axis["right"].toggle(ticklabels=False, label=False)
|
||||
|
||||
def twiny(self, axes_class=None):
|
||||
"""
|
||||
create a twin of Axes for generating a plot with a shared
|
||||
y-axis but independent x axis. The x-axis of self will have
|
||||
ticks on bottom and the returned axes will have ticks on the
|
||||
top
|
||||
"""
|
||||
|
||||
if axes_class is None:
|
||||
axes_class = self._get_base_axes()
|
||||
|
||||
parasite_axes_class = parasite_axes_class_factory(axes_class)
|
||||
|
||||
ax2 = parasite_axes_class(self, sharey=self, frameon=False)
|
||||
self.parasites.append(ax2)
|
||||
ax2._remove_method = self._remove_twiny
|
||||
|
||||
self.axis["top"].set_visible(False)
|
||||
|
||||
ax2.axis["top"].set_visible(True)
|
||||
ax2.axis["left", "right", "bottom"].set_visible(False)
|
||||
|
||||
return ax2
|
||||
|
||||
def _remove_twiny(self, ax):
|
||||
self.parasites.remove(ax)
|
||||
self.axis["top"].set_visible(True)
|
||||
self.axis["top"].toggle(ticklabels=False, label=False)
|
||||
|
||||
def twin(self, aux_trans=None, axes_class=None):
|
||||
"""
|
||||
create a twin of Axes for generating a plot with a sharex
|
||||
x-axis but independent y axis. The y-axis of self will have
|
||||
ticks on left and the returned axes will have ticks on the
|
||||
right
|
||||
"""
|
||||
|
||||
if axes_class is None:
|
||||
axes_class = self._get_base_axes()
|
||||
|
||||
parasite_axes_auxtrans_class = \
|
||||
parasite_axes_auxtrans_class_factory(axes_class)
|
||||
|
||||
if aux_trans is None:
|
||||
ax2 = parasite_axes_auxtrans_class(
|
||||
self, mtransforms.IdentityTransform(), viewlim_mode="equal")
|
||||
else:
|
||||
ax2 = parasite_axes_auxtrans_class(
|
||||
self, aux_trans, viewlim_mode="transform")
|
||||
self.parasites.append(ax2)
|
||||
ax2._remove_method = self.parasites.remove
|
||||
|
||||
self.axis["top", "right"].set_visible(False)
|
||||
|
||||
ax2.axis["top", "right"].set_visible(True)
|
||||
ax2.axis["left", "bottom"].set_visible(False)
|
||||
|
||||
def _remove_method(h):
|
||||
self.parasites.remove(h)
|
||||
self.axis["top", "right"].set_visible(True)
|
||||
self.axis["top", "right"].toggle(ticklabels=False, label=False)
|
||||
ax2._remove_method = _remove_method
|
||||
|
||||
return ax2
|
||||
|
||||
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]
|
||||
bbs.append(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:
|
||||
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=axes_class)
|
||||
subplot_host_class = subplot_class_factory(host_axes_class)
|
||||
return subplot_host_class
|
||||
|
||||
|
||||
HostAxes = host_axes_class_factory(axes_class=Axes)
|
||||
SubplotHost = subplot_class_factory(HostAxes)
|
||||
|
||||
|
||||
def host_axes(*args, axes_class=None, 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=None, 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