Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
11
venv/Lib/site-packages/mpl_toolkits/axes_grid/__init__.py
Normal file
11
venv/Lib/site-packages/mpl_toolkits/axes_grid/__init__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from . import axes_size as Size
|
||||
from .axes_divider import Divider, SubplotDivider, make_axes_locatable
|
||||
from .axes_grid import Grid, ImageGrid, AxesGrid
|
||||
#from axes_divider import make_axes_locatable
|
||||
from matplotlib.cbook import warn_deprecated
|
||||
warn_deprecated(since='2.1',
|
||||
name='mpl_toolkits.axes_grid',
|
||||
alternative='mpl_toolkits.axes_grid1 and'
|
||||
' mpl_toolkits.axisartist, which provide'
|
||||
' the same functionality',
|
||||
obj_type='module')
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,6 @@
|
|||
from matplotlib.offsetbox import AnchoredOffsetbox, AuxTransformBox, VPacker,\
|
||||
TextArea, AnchoredText, DrawingArea, AnnotationBbox
|
||||
|
||||
from mpl_toolkits.axes_grid1.anchored_artists import \
|
||||
AnchoredDrawingArea, AnchoredAuxTransformBox, \
|
||||
AnchoredEllipse, AnchoredSizeBar
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.angle_helper import *
|
|
@ -0,0 +1,3 @@
|
|||
from mpl_toolkits.axes_grid1.axes_divider import (
|
||||
AxesDivider, AxesLocator, Divider, SubplotDivider, make_axes_locatable)
|
||||
from mpl_toolkits.axisartist.axislines import Axes
|
|
@ -0,0 +1,2 @@
|
|||
from mpl_toolkits.axisartist.axes_grid import (
|
||||
AxesGrid, CbarAxes, Grid, ImageGrid)
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.axes_rgb import *
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axes_grid1.axes_size import *
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.axis_artist import *
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.axisline_style import *
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.axislines import *
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.clip_path import *
|
|
@ -0,0 +1,5 @@
|
|||
from mpl_toolkits.axes_grid1.colorbar import (
|
||||
make_axes_kw_doc, colormap_kw_doc, colorbar_doc,
|
||||
CbarAxesLocator, ColorbarBase, Colorbar,
|
||||
make_axes, colorbar
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.floating_axes import *
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.grid_finder import *
|
|
@ -0,0 +1 @@
|
|||
from mpl_toolkits.axisartist.grid_helper_curvelinear import *
|
|
@ -0,0 +1,4 @@
|
|||
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition, \
|
||||
AnchoredSizeLocator, \
|
||||
AnchoredZoomLocator, BboxPatch, BboxConnector, BboxConnectorPatch, \
|
||||
inset_axes, zoomed_inset_axes, mark_inset
|
|
@ -0,0 +1,10 @@
|
|||
from mpl_toolkits.axes_grid1.parasite_axes import (
|
||||
host_axes_class_factory, parasite_axes_class_factory,
|
||||
parasite_axes_auxtrans_class_factory, subplot_class_factory)
|
||||
from mpl_toolkits.axisartist.axislines import Axes
|
||||
|
||||
|
||||
ParasiteAxes = parasite_axes_class_factory(Axes)
|
||||
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes)
|
||||
HostAxes = host_axes_class_factory(Axes)
|
||||
SubplotHost = subplot_class_factory(HostAxes)
|
|
@ -0,0 +1,5 @@
|
|||
from . import axes_size as Size
|
||||
from .axes_divider import Divider, SubplotDivider, make_axes_locatable
|
||||
from .axes_grid import Grid, ImageGrid, AxesGrid
|
||||
|
||||
from .parasite_axes import host_subplot, host_axes
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,564 @@
|
|||
from matplotlib import transforms
|
||||
from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox,
|
||||
DrawingArea, TextArea, VPacker)
|
||||
from matplotlib.patches import (Rectangle, Ellipse, ArrowStyle,
|
||||
FancyArrowPatch, PathPatch)
|
||||
from matplotlib.text import TextPath
|
||||
|
||||
__all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox',
|
||||
'AnchoredEllipse', 'AnchoredSizeBar', 'AnchoredDirectionArrows']
|
||||
|
||||
|
||||
class AnchoredDrawingArea(AnchoredOffsetbox):
|
||||
def __init__(self, width, height, xdescent, ydescent,
|
||||
loc, pad=0.4, borderpad=0.5, prop=None, frameon=True,
|
||||
**kwargs):
|
||||
"""
|
||||
An anchored container with a fixed size and fillable DrawingArea.
|
||||
|
||||
Artists added to the *drawing_area* will have their coordinates
|
||||
interpreted as pixels. Any transformations set on the artists will be
|
||||
overridden.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width, height : float
|
||||
width and height of the container, in pixels.
|
||||
|
||||
xdescent, ydescent : float
|
||||
descent of the container in the x- and y- direction, in pixels.
|
||||
|
||||
loc : int
|
||||
Location of this artist. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
pad : float, default: 0.4
|
||||
Padding around the child objects, in fraction of the font size.
|
||||
|
||||
borderpad : float, default: 0.5
|
||||
Border padding, in fraction of the font size.
|
||||
|
||||
prop : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font property used as a reference for paddings.
|
||||
|
||||
frameon : bool, default: True
|
||||
If True, draw a box around this artists.
|
||||
|
||||
**kwargs
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
drawing_area : `matplotlib.offsetbox.DrawingArea`
|
||||
A container for artists to display.
|
||||
|
||||
Examples
|
||||
--------
|
||||
To display blue and red circles of different sizes in the upper right
|
||||
of an axes *ax*:
|
||||
|
||||
>>> ada = AnchoredDrawingArea(20, 20, 0, 0,
|
||||
... loc='upper right', frameon=False)
|
||||
>>> ada.drawing_area.add_artist(Circle((10, 10), 10, fc="b"))
|
||||
>>> ada.drawing_area.add_artist(Circle((30, 10), 5, fc="r"))
|
||||
>>> ax.add_artist(ada)
|
||||
"""
|
||||
self.da = DrawingArea(width, height, xdescent, ydescent)
|
||||
self.drawing_area = self.da
|
||||
|
||||
super().__init__(
|
||||
loc, pad=pad, borderpad=borderpad, child=self.da, prop=None,
|
||||
frameon=frameon, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class AnchoredAuxTransformBox(AnchoredOffsetbox):
|
||||
def __init__(self, transform, loc,
|
||||
pad=0.4, borderpad=0.5, prop=None, frameon=True, **kwargs):
|
||||
"""
|
||||
An anchored container with transformed coordinates.
|
||||
|
||||
Artists added to the *drawing_area* are scaled according to the
|
||||
coordinates of the transformation used. The dimensions of this artist
|
||||
will scale to contain the artists added.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
transform : `matplotlib.transforms.Transform`
|
||||
The transformation object for the coordinate system in use, i.e.,
|
||||
:attr:`matplotlib.axes.Axes.transData`.
|
||||
|
||||
loc : int
|
||||
Location of this artist. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
pad : float, default: 0.4
|
||||
Padding around the child objects, in fraction of the font size.
|
||||
|
||||
borderpad : float, default: 0.5
|
||||
Border padding, in fraction of the font size.
|
||||
|
||||
prop : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font property used as a reference for paddings.
|
||||
|
||||
frameon : bool, default: True
|
||||
If True, draw a box around this artists.
|
||||
|
||||
**kwargs
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
drawing_area : `matplotlib.offsetbox.AuxTransformBox`
|
||||
A container for artists to display.
|
||||
|
||||
Examples
|
||||
--------
|
||||
To display an ellipse in the upper left, with a width of 0.1 and
|
||||
height of 0.4 in data coordinates:
|
||||
|
||||
>>> box = AnchoredAuxTransformBox(ax.transData, loc='upper left')
|
||||
>>> el = Ellipse((0, 0), width=0.1, height=0.4, angle=30)
|
||||
>>> box.drawing_area.add_artist(el)
|
||||
>>> ax.add_artist(box)
|
||||
"""
|
||||
self.drawing_area = AuxTransformBox(transform)
|
||||
|
||||
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
|
||||
child=self.drawing_area,
|
||||
prop=prop,
|
||||
frameon=frameon,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class AnchoredEllipse(AnchoredOffsetbox):
|
||||
def __init__(self, transform, width, height, angle, loc,
|
||||
pad=0.1, borderpad=0.1, prop=None, frameon=True, **kwargs):
|
||||
"""
|
||||
Draw an anchored ellipse of a given size.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
transform : `matplotlib.transforms.Transform`
|
||||
The transformation object for the coordinate system in use, i.e.,
|
||||
:attr:`matplotlib.axes.Axes.transData`.
|
||||
|
||||
width, height : float
|
||||
Width and height of the ellipse, given in coordinates of
|
||||
*transform*.
|
||||
|
||||
angle : float
|
||||
Rotation of the ellipse, in degrees, anti-clockwise.
|
||||
|
||||
loc : int
|
||||
Location of this size bar. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
pad : float, optional
|
||||
Padding around the ellipse, in fraction of the font size. Defaults
|
||||
to 0.1.
|
||||
|
||||
borderpad : float, default: 0.1
|
||||
Border padding, in fraction of the font size.
|
||||
|
||||
frameon : bool, default: True
|
||||
If True, draw a box around the ellipse.
|
||||
|
||||
prop : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font property used as a reference for paddings.
|
||||
|
||||
**kwargs
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
ellipse : `matplotlib.patches.Ellipse`
|
||||
Ellipse patch drawn.
|
||||
"""
|
||||
self._box = AuxTransformBox(transform)
|
||||
self.ellipse = Ellipse((0, 0), width, height, angle)
|
||||
self._box.add_artist(self.ellipse)
|
||||
|
||||
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
|
||||
child=self._box,
|
||||
prop=prop,
|
||||
frameon=frameon, **kwargs)
|
||||
|
||||
|
||||
class AnchoredSizeBar(AnchoredOffsetbox):
|
||||
def __init__(self, transform, size, label, loc,
|
||||
pad=0.1, borderpad=0.1, sep=2,
|
||||
frameon=True, size_vertical=0, color='black',
|
||||
label_top=False, fontproperties=None, fill_bar=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Draw a horizontal scale bar with a center-aligned label underneath.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
transform : `matplotlib.transforms.Transform`
|
||||
The transformation object for the coordinate system in use, i.e.,
|
||||
:attr:`matplotlib.axes.Axes.transData`.
|
||||
|
||||
size : float
|
||||
Horizontal length of the size bar, given in coordinates of
|
||||
*transform*.
|
||||
|
||||
label : str
|
||||
Label to display.
|
||||
|
||||
loc : int
|
||||
Location of this size bar. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
pad : float, default: 0.1
|
||||
Padding around the label and size bar, in fraction of the font
|
||||
size.
|
||||
|
||||
borderpad : float, default: 0.1
|
||||
Border padding, in fraction of the font size.
|
||||
|
||||
sep : float, default: 2
|
||||
Separation between the label and the size bar, in points.
|
||||
|
||||
frameon : bool, default: True
|
||||
If True, draw a box around the horizontal bar and label.
|
||||
|
||||
size_vertical : float, default: 0
|
||||
Vertical length of the size bar, given in coordinates of
|
||||
*transform*.
|
||||
|
||||
color : str, default: 'black'
|
||||
Color for the size bar and label.
|
||||
|
||||
label_top : bool, default: False
|
||||
If True, the label will be over the size bar.
|
||||
|
||||
fontproperties : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font properties for the label text.
|
||||
|
||||
fill_bar : bool, optional
|
||||
If True and if size_vertical is nonzero, the size bar will
|
||||
be filled in with the color specified by the size bar.
|
||||
Defaults to True if `size_vertical` is greater than
|
||||
zero and False otherwise.
|
||||
|
||||
**kwargs
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
size_bar : `matplotlib.offsetbox.AuxTransformBox`
|
||||
Container for the size bar.
|
||||
|
||||
txt_label : `matplotlib.offsetbox.TextArea`
|
||||
Container for the label of the size bar.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If *prop* is passed as a keyworded argument, but *fontproperties* is
|
||||
not, then *prop* is be assumed to be the intended *fontproperties*.
|
||||
Using both *prop* and *fontproperties* is not supported.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import matplotlib.pyplot as plt
|
||||
>>> import numpy as np
|
||||
>>> from mpl_toolkits.axes_grid1.anchored_artists import (
|
||||
... AnchoredSizeBar)
|
||||
>>> fig, ax = plt.subplots()
|
||||
>>> ax.imshow(np.random.random((10, 10)))
|
||||
>>> bar = AnchoredSizeBar(ax.transData, 3, '3 data units', 4)
|
||||
>>> ax.add_artist(bar)
|
||||
>>> fig.show()
|
||||
|
||||
Using all the optional parameters
|
||||
|
||||
>>> import matplotlib.font_manager as fm
|
||||
>>> fontprops = fm.FontProperties(size=14, family='monospace')
|
||||
>>> bar = AnchoredSizeBar(ax.transData, 3, '3 units', 4, pad=0.5,
|
||||
... sep=5, borderpad=0.5, frameon=False,
|
||||
... size_vertical=0.5, color='white',
|
||||
... fontproperties=fontprops)
|
||||
"""
|
||||
if fill_bar is None:
|
||||
fill_bar = size_vertical > 0
|
||||
|
||||
self.size_bar = AuxTransformBox(transform)
|
||||
self.size_bar.add_artist(Rectangle((0, 0), size, size_vertical,
|
||||
fill=fill_bar, facecolor=color,
|
||||
edgecolor=color))
|
||||
|
||||
if fontproperties is None and 'prop' in kwargs:
|
||||
fontproperties = kwargs.pop('prop')
|
||||
|
||||
if fontproperties is None:
|
||||
textprops = {'color': color}
|
||||
else:
|
||||
textprops = {'color': color, 'fontproperties': fontproperties}
|
||||
|
||||
self.txt_label = TextArea(
|
||||
label,
|
||||
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):
|
||||
def __init__(self, transform, label_x, label_y, length=0.15,
|
||||
fontsize=0.08, loc=2, angle=0, aspect_ratio=1, pad=0.4,
|
||||
borderpad=0.4, frameon=False, color='w', alpha=1,
|
||||
sep_x=0.01, sep_y=0, fontproperties=None, back_length=0.15,
|
||||
head_width=10, head_length=15, tail_width=2,
|
||||
text_props=None, arrow_props=None,
|
||||
**kwargs):
|
||||
"""
|
||||
Draw two perpendicular arrows to indicate directions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
transform : `matplotlib.transforms.Transform`
|
||||
The transformation object for the coordinate system in use, i.e.,
|
||||
:attr:`matplotlib.axes.Axes.transAxes`.
|
||||
|
||||
label_x, label_y : str
|
||||
Label text for the x and y arrows
|
||||
|
||||
length : float, default: 0.15
|
||||
Length of the arrow, given in coordinates of *transform*.
|
||||
|
||||
fontsize : float, default: 0.08
|
||||
Size of label strings, given in coordinates of *transform*.
|
||||
|
||||
loc : int, default: 2
|
||||
Location of the direction arrows. Valid location codes are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
angle : float, default: 0
|
||||
The angle of the arrows in degrees.
|
||||
|
||||
aspect_ratio : float, default: 1
|
||||
The ratio of the length of arrow_x and arrow_y.
|
||||
Negative numbers can be used to change the direction.
|
||||
|
||||
pad : float, default: 0.4
|
||||
Padding around the labels and arrows, in fraction of the font size.
|
||||
|
||||
borderpad : float, default: 0.4
|
||||
Border padding, in fraction of the font size.
|
||||
|
||||
frameon : bool, default: False
|
||||
If True, draw a box around the arrows and labels.
|
||||
|
||||
color : str, default: 'white'
|
||||
Color for the arrows and labels.
|
||||
|
||||
alpha : float, default: 1
|
||||
Alpha values of the arrows and labels
|
||||
|
||||
sep_x, sep_y : float, default: 0.01 and 0 respectively
|
||||
Separation between the arrows and labels in coordinates of
|
||||
*transform*.
|
||||
|
||||
fontproperties : `matplotlib.font_manager.FontProperties`, optional
|
||||
Font properties for the label text.
|
||||
|
||||
back_length : float, default: 0.15
|
||||
Fraction of the arrow behind the arrow crossing.
|
||||
|
||||
head_width : float, default: 10
|
||||
Width of arrow head, sent to ArrowStyle.
|
||||
|
||||
head_length : float, default: 15
|
||||
Length of arrow head, sent to ArrowStyle.
|
||||
|
||||
tail_width : float, default: 2
|
||||
Width of arrow tail, sent to ArrowStyle.
|
||||
|
||||
text_props, arrow_props : dict
|
||||
Properties of the text and arrows, passed to
|
||||
`.textpath.TextPath` and `.patches.FancyArrowPatch`.
|
||||
|
||||
**kwargs
|
||||
Keyworded arguments to pass to
|
||||
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
arrow_x, arrow_y : `matplotlib.patches.FancyArrowPatch`
|
||||
Arrow x and y
|
||||
|
||||
text_path_x, text_path_y : `matplotlib.textpath.TextPath`
|
||||
Path for arrow labels
|
||||
|
||||
p_x, p_y : `matplotlib.patches.PathPatch`
|
||||
Patch for arrow labels
|
||||
|
||||
box : `matplotlib.offsetbox.AuxTransformBox`
|
||||
Container for the arrows and labels.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If *prop* is passed as a keyword argument, but *fontproperties* is
|
||||
not, then *prop* is be assumed to be the intended *fontproperties*.
|
||||
Using both *prop* and *fontproperties* is not supported.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import matplotlib.pyplot as plt
|
||||
>>> import numpy as np
|
||||
>>> from mpl_toolkits.axes_grid1.anchored_artists import (
|
||||
... AnchoredDirectionArrows)
|
||||
>>> fig, ax = plt.subplots()
|
||||
>>> ax.imshow(np.random.random((10, 10)))
|
||||
>>> arrows = AnchoredDirectionArrows(ax.transAxes, '111', '110')
|
||||
>>> ax.add_artist(arrows)
|
||||
>>> fig.show()
|
||||
|
||||
Using several of the optional parameters, creating downward pointing
|
||||
arrow and high contrast text labels.
|
||||
|
||||
>>> import matplotlib.font_manager as fm
|
||||
>>> fontprops = fm.FontProperties(family='monospace')
|
||||
>>> arrows = AnchoredDirectionArrows(ax.transAxes, 'East', 'South',
|
||||
... loc='lower left', color='k',
|
||||
... aspect_ratio=-1, sep_x=0.02,
|
||||
... sep_y=-0.01,
|
||||
... text_props={'ec':'w', 'fc':'k'},
|
||||
... fontproperties=fontprops)
|
||||
"""
|
||||
if arrow_props is None:
|
||||
arrow_props = {}
|
||||
|
||||
if text_props is None:
|
||||
text_props = {}
|
||||
|
||||
arrowstyle = ArrowStyle("Simple",
|
||||
head_width=head_width,
|
||||
head_length=head_length,
|
||||
tail_width=tail_width)
|
||||
|
||||
if fontproperties is None and 'prop' in kwargs:
|
||||
fontproperties = kwargs.pop('prop')
|
||||
|
||||
if 'color' not in arrow_props:
|
||||
arrow_props['color'] = color
|
||||
|
||||
if 'alpha' not in arrow_props:
|
||||
arrow_props['alpha'] = alpha
|
||||
|
||||
if 'color' not in text_props:
|
||||
text_props['color'] = color
|
||||
|
||||
if 'alpha' not in text_props:
|
||||
text_props['alpha'] = alpha
|
||||
|
||||
t_start = transform
|
||||
t_end = t_start + transforms.Affine2D().rotate_deg(angle)
|
||||
|
||||
self.box = AuxTransformBox(t_end)
|
||||
|
||||
length_x = length
|
||||
length_y = length*aspect_ratio
|
||||
|
||||
self.arrow_x = FancyArrowPatch(
|
||||
(0, back_length*length_y),
|
||||
(length_x, back_length*length_y),
|
||||
arrowstyle=arrowstyle,
|
||||
shrinkA=0.0,
|
||||
shrinkB=0.0,
|
||||
**arrow_props)
|
||||
|
||||
self.arrow_y = FancyArrowPatch(
|
||||
(back_length*length_x, 0),
|
||||
(back_length*length_x, length_y),
|
||||
arrowstyle=arrowstyle,
|
||||
shrinkA=0.0,
|
||||
shrinkB=0.0,
|
||||
**arrow_props)
|
||||
|
||||
self.box.add_artist(self.arrow_x)
|
||||
self.box.add_artist(self.arrow_y)
|
||||
|
||||
text_path_x = TextPath((
|
||||
length_x+sep_x, back_length*length_y+sep_y), label_x,
|
||||
size=fontsize, prop=fontproperties)
|
||||
self.p_x = PathPatch(text_path_x, transform=t_start, **text_props)
|
||||
self.box.add_artist(self.p_x)
|
||||
|
||||
text_path_y = TextPath((
|
||||
length_x*back_length+sep_x, length_y*(1-back_length)+sep_y),
|
||||
label_y, size=fontsize, prop=fontproperties)
|
||||
self.p_y = PathPatch(text_path_y, **text_props)
|
||||
self.box.add_artist(self.p_y)
|
||||
|
||||
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
|
||||
child=self.box,
|
||||
frameon=frameon, **kwargs)
|
757
venv/Lib/site-packages/mpl_toolkits/axes_grid1/axes_divider.py
Normal file
757
venv/Lib/site-packages/mpl_toolkits/axes_grid1/axes_divider.py
Normal file
|
@ -0,0 +1,757 @@
|
|||
"""
|
||||
Helper classes to adjust the positions of multiple axes at drawing time.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import cbook
|
||||
from matplotlib.axes import SubplotBase
|
||||
from matplotlib.gridspec import SubplotSpec, GridSpec
|
||||
import matplotlib.transforms as mtransforms
|
||||
from . import axes_size as Size
|
||||
|
||||
|
||||
class Divider:
|
||||
"""
|
||||
An Axes positioning class.
|
||||
|
||||
The divider is initialized with lists of horizontal and vertical sizes
|
||||
(:mod:`mpl_toolkits.axes_grid1.axes_size`) based on which a given
|
||||
rectangular area will be divided.
|
||||
|
||||
The `new_locator` method then creates a callable object
|
||||
that can be used as the *axes_locator* of the axes.
|
||||
"""
|
||||
|
||||
def __init__(self, fig, pos, horizontal, vertical,
|
||||
aspect=None, anchor="C"):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
fig : Figure
|
||||
pos : tuple of 4 floats
|
||||
Position of the rectangle that will be divided.
|
||||
horizontal : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`
|
||||
Sizes for horizontal division.
|
||||
vertical : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`
|
||||
Sizes for vertical division.
|
||||
aspect : bool
|
||||
Whether overall rectangular area is reduced so that the relative
|
||||
part of the horizontal and vertical scales have the same scale.
|
||||
anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'}
|
||||
Placement of the reduced rectangle, when *aspect* is True.
|
||||
"""
|
||||
|
||||
self._fig = fig
|
||||
self._pos = pos
|
||||
self._horizontal = horizontal
|
||||
self._vertical = vertical
|
||||
self._anchor = anchor
|
||||
self._aspect = aspect
|
||||
self._xrefindex = 0
|
||||
self._yrefindex = 0
|
||||
self._locator = None
|
||||
|
||||
def get_horizontal_sizes(self, renderer):
|
||||
return [s.get_size(renderer) for s in self.get_horizontal()]
|
||||
|
||||
def get_vertical_sizes(self, renderer):
|
||||
return [s.get_size(renderer) for s in self.get_vertical()]
|
||||
|
||||
def get_vsize_hsize(self):
|
||||
vsize = Size.AddList(self.get_vertical())
|
||||
hsize = Size.AddList(self.get_horizontal())
|
||||
return vsize, hsize
|
||||
|
||||
@staticmethod
|
||||
def _calc_k(l, total_size):
|
||||
|
||||
rs_sum, as_sum = 0., 0.
|
||||
|
||||
for _rs, _as in l:
|
||||
rs_sum += _rs
|
||||
as_sum += _as
|
||||
|
||||
if rs_sum != 0.:
|
||||
k = (total_size - as_sum) / rs_sum
|
||||
return k
|
||||
else:
|
||||
return 0.
|
||||
|
||||
@staticmethod
|
||||
def _calc_offsets(l, k):
|
||||
offsets = [0.]
|
||||
for _rs, _as in l:
|
||||
offsets.append(offsets[-1] + _rs*k + _as)
|
||||
return offsets
|
||||
|
||||
def set_position(self, pos):
|
||||
"""
|
||||
Set the position of the rectangle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pos : tuple of 4 floats
|
||||
position of the rectangle that will be divided
|
||||
"""
|
||||
self._pos = pos
|
||||
|
||||
def get_position(self):
|
||||
"""Return the position of the rectangle."""
|
||||
return self._pos
|
||||
|
||||
def set_anchor(self, anchor):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'}
|
||||
anchor position
|
||||
|
||||
===== ============
|
||||
value description
|
||||
===== ============
|
||||
'C' Center
|
||||
'SW' bottom left
|
||||
'S' bottom
|
||||
'SE' bottom right
|
||||
'E' right
|
||||
'NE' top right
|
||||
'N' top
|
||||
'NW' top left
|
||||
'W' left
|
||||
===== ============
|
||||
|
||||
"""
|
||||
if len(anchor) != 2:
|
||||
cbook._check_in_list(mtransforms.Bbox.coefs, anchor=anchor)
|
||||
self._anchor = anchor
|
||||
|
||||
def get_anchor(self):
|
||||
"""Return the anchor."""
|
||||
return self._anchor
|
||||
|
||||
def set_horizontal(self, h):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
h : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`
|
||||
sizes for horizontal division
|
||||
"""
|
||||
self._horizontal = h
|
||||
|
||||
def get_horizontal(self):
|
||||
"""Return horizontal sizes."""
|
||||
return self._horizontal
|
||||
|
||||
def set_vertical(self, v):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
v : list of :mod:`~mpl_toolkits.axes_grid1.axes_size`
|
||||
sizes for vertical division
|
||||
"""
|
||||
self._vertical = v
|
||||
|
||||
def get_vertical(self):
|
||||
"""Return vertical sizes."""
|
||||
return self._vertical
|
||||
|
||||
def set_aspect(self, aspect=False):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
aspect : bool
|
||||
"""
|
||||
self._aspect = aspect
|
||||
|
||||
def get_aspect(self):
|
||||
"""Return aspect."""
|
||||
return self._aspect
|
||||
|
||||
def set_locator(self, _locator):
|
||||
self._locator = _locator
|
||||
|
||||
def get_locator(self):
|
||||
return self._locator
|
||||
|
||||
def get_position_runtime(self, ax, renderer):
|
||||
if self._locator is None:
|
||||
return self.get_position()
|
||||
else:
|
||||
return self._locator(ax, renderer).bounds
|
||||
|
||||
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
nx, nx1 : int
|
||||
Integers specifying the column-position of the
|
||||
cell. When *nx1* is None, a single *nx*-th column is
|
||||
specified. Otherwise location of columns spanning between *nx*
|
||||
to *nx1* (but excluding *nx1*-th column) is specified.
|
||||
ny, ny1 : int
|
||||
Same as *nx* and *nx1*, but for row positions.
|
||||
axes
|
||||
renderer
|
||||
"""
|
||||
|
||||
figW, figH = self._fig.get_size_inches()
|
||||
x, y, w, h = self.get_position_runtime(axes, renderer)
|
||||
|
||||
hsizes = self.get_horizontal_sizes(renderer)
|
||||
vsizes = self.get_vertical_sizes(renderer)
|
||||
k_h = self._calc_k(hsizes, figW*w)
|
||||
k_v = self._calc_k(vsizes, figH*h)
|
||||
|
||||
if self.get_aspect():
|
||||
k = min(k_h, k_v)
|
||||
ox = self._calc_offsets(hsizes, k)
|
||||
oy = self._calc_offsets(vsizes, k)
|
||||
|
||||
ww = (ox[-1] - ox[0]) / figW
|
||||
hh = (oy[-1] - oy[0]) / figH
|
||||
pb = mtransforms.Bbox.from_bounds(x, y, w, h)
|
||||
pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
|
||||
pb1_anchored = pb1.anchored(self.get_anchor(), pb)
|
||||
x0, y0 = pb1_anchored.x0, pb1_anchored.y0
|
||||
|
||||
else:
|
||||
ox = self._calc_offsets(hsizes, k_h)
|
||||
oy = self._calc_offsets(vsizes, k_v)
|
||||
x0, y0 = x, y
|
||||
|
||||
if nx1 is None:
|
||||
nx1 = nx + 1
|
||||
if ny1 is None:
|
||||
ny1 = ny + 1
|
||||
|
||||
x1, w1 = x0 + ox[nx] / figW, (ox[nx1] - ox[nx]) / figW
|
||||
y1, h1 = y0 + oy[ny] / figH, (oy[ny1] - oy[ny]) / figH
|
||||
|
||||
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
|
||||
|
||||
def new_locator(self, nx, ny, nx1=None, ny1=None):
|
||||
"""
|
||||
Return a new `AxesLocator` for the specified cell.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nx, nx1 : int
|
||||
Integers specifying the column-position of the
|
||||
cell. When *nx1* is None, a single *nx*-th column is
|
||||
specified. Otherwise location of columns spanning between *nx*
|
||||
to *nx1* (but excluding *nx1*-th column) is specified.
|
||||
ny, ny1 : int
|
||||
Same as *nx* and *nx1*, but for row positions.
|
||||
"""
|
||||
return AxesLocator(self, nx, ny, nx1, ny1)
|
||||
|
||||
def append_size(self, position, size):
|
||||
if position == "left":
|
||||
self._horizontal.insert(0, size)
|
||||
self._xrefindex += 1
|
||||
elif position == "right":
|
||||
self._horizontal.append(size)
|
||||
elif position == "bottom":
|
||||
self._vertical.insert(0, size)
|
||||
self._yrefindex += 1
|
||||
elif position == "top":
|
||||
self._vertical.append(size)
|
||||
else:
|
||||
cbook._check_in_list(["left", "right", "bottom", "top"],
|
||||
position=position)
|
||||
|
||||
def add_auto_adjustable_area(self, use_axes, pad=0.1, adjust_dirs=None):
|
||||
if adjust_dirs is None:
|
||||
adjust_dirs = ["left", "right", "bottom", "top"]
|
||||
from .axes_size import Padded, SizeFromFunc, GetExtentHelper
|
||||
for d in adjust_dirs:
|
||||
helper = GetExtentHelper(use_axes, d)
|
||||
size = SizeFromFunc(helper)
|
||||
padded_size = Padded(size, pad) # pad in inch
|
||||
self.append_size(d, padded_size)
|
||||
|
||||
|
||||
class AxesLocator:
|
||||
"""
|
||||
A simple callable object, initialized with AxesDivider class,
|
||||
returns the position and size of the given cell.
|
||||
"""
|
||||
def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
axes_divider : AxesDivider
|
||||
nx, nx1 : int
|
||||
Integers specifying the column-position of the
|
||||
cell. When *nx1* is None, a single *nx*-th column is
|
||||
specified. Otherwise location of columns spanning between *nx*
|
||||
to *nx1* (but excluding *nx1*-th column) is specified.
|
||||
ny, ny1 : int
|
||||
Same as *nx* and *nx1*, but for row positions.
|
||||
"""
|
||||
self._axes_divider = axes_divider
|
||||
|
||||
_xrefindex = axes_divider._xrefindex
|
||||
_yrefindex = axes_divider._yrefindex
|
||||
|
||||
self._nx, self._ny = nx - _xrefindex, ny - _yrefindex
|
||||
|
||||
if nx1 is None:
|
||||
nx1 = nx + 1
|
||||
if ny1 is None:
|
||||
ny1 = ny + 1
|
||||
|
||||
self._nx1 = nx1 - _xrefindex
|
||||
self._ny1 = ny1 - _yrefindex
|
||||
|
||||
def __call__(self, axes, renderer):
|
||||
|
||||
_xrefindex = self._axes_divider._xrefindex
|
||||
_yrefindex = self._axes_divider._yrefindex
|
||||
|
||||
return self._axes_divider.locate(self._nx + _xrefindex,
|
||||
self._ny + _yrefindex,
|
||||
self._nx1 + _xrefindex,
|
||||
self._ny1 + _yrefindex,
|
||||
axes,
|
||||
renderer)
|
||||
|
||||
def get_subplotspec(self):
|
||||
if hasattr(self._axes_divider, "get_subplotspec"):
|
||||
return self._axes_divider.get_subplotspec()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class SubplotDivider(Divider):
|
||||
"""
|
||||
The Divider class whose rectangle area is specified as a subplot geometry.
|
||||
"""
|
||||
|
||||
def __init__(self, fig, *args, horizontal=None, vertical=None,
|
||||
aspect=None, anchor='C'):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
fig : `matplotlib.figure.Figure`
|
||||
|
||||
*args : tuple (*nrows*, *ncols*, *index*) or int
|
||||
The array of subplots in the figure has dimensions ``(nrows,
|
||||
ncols)``, and *index* is the index of the subplot being created.
|
||||
*index* starts at 1 in the upper left corner and increases to the
|
||||
right.
|
||||
|
||||
If *nrows*, *ncols*, and *index* are all single digit numbers, then
|
||||
*args* can be passed as a single 3-digit number (e.g. 234 for
|
||||
(2, 3, 4)).
|
||||
"""
|
||||
self.figure = fig
|
||||
self._subplotspec = SubplotSpec._from_subplot_args(fig, args)
|
||||
self.update_params() # sets self.figbox
|
||||
Divider.__init__(self, fig, pos=self.figbox.bounds,
|
||||
horizontal=horizontal or [], vertical=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."""
|
||||
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_grid1.axes_size` or float or str
|
||||
A width of the axes. If float or string is given, *from_any*
|
||||
function is used to create the size, with *ref_size* set to AxesX
|
||||
instance of the current axes.
|
||||
pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
|
||||
Pad between the axes. It takes same argument as *size*.
|
||||
pack_start : bool
|
||||
If False, the new axes is appended at the end
|
||||
of the list, i.e., it became the right-most axes. If True, it is
|
||||
inserted at the start of the list, and becomes the left-most axes.
|
||||
**kwargs
|
||||
All extra keywords arguments are passed to the created axes.
|
||||
If *axes_class* is given, the new axes will be created as an
|
||||
instance of the given class. Otherwise, the same class of the
|
||||
main axes will be used.
|
||||
"""
|
||||
if pad is None:
|
||||
cbook.warn_deprecated(
|
||||
"3.2", message="In a future version, 'pad' will default to "
|
||||
"rcParams['figure.subplot.wspace']. Set pad=0 to keep the "
|
||||
"old behavior.")
|
||||
if pad:
|
||||
if not isinstance(pad, Size._Base):
|
||||
pad = Size.from_any(pad, fraction_ref=self._xref)
|
||||
if pack_start:
|
||||
self._horizontal.insert(0, pad)
|
||||
self._xrefindex += 1
|
||||
else:
|
||||
self._horizontal.append(pad)
|
||||
if not isinstance(size, Size._Base):
|
||||
size = Size.from_any(size, fraction_ref=self._xref)
|
||||
if pack_start:
|
||||
self._horizontal.insert(0, size)
|
||||
self._xrefindex += 1
|
||||
locator = self.new_locator(nx=0, ny=self._yrefindex)
|
||||
else:
|
||||
self._horizontal.append(size)
|
||||
locator = self.new_locator(
|
||||
nx=len(self._horizontal) - 1, ny=self._yrefindex)
|
||||
ax = self._get_new_axes(**kwargs)
|
||||
ax.set_axes_locator(locator)
|
||||
return ax
|
||||
|
||||
def new_vertical(self, size, pad=None, pack_start=False, **kwargs):
|
||||
"""
|
||||
Add a new axes on the top (or bottom) side of the main axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
size : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
|
||||
A height of the axes. If float or string is given, *from_any*
|
||||
function is used to create the size, with *ref_size* set to AxesX
|
||||
instance of the current axes.
|
||||
pad : :mod:`~mpl_toolkits.axes_grid1.axes_size` or float or str
|
||||
Pad between the axes. It takes same argument as *size*.
|
||||
pack_start : bool
|
||||
If False, the new axes is appended at the end
|
||||
of the list, i.e., it became the right-most axes. If True, it is
|
||||
inserted at the start of the list, and becomes the left-most axes.
|
||||
**kwargs
|
||||
All extra keywords arguments are passed to the created axes.
|
||||
If *axes_class* is given, the new axes will be created as an
|
||||
instance of the given class. Otherwise, the same class of the
|
||||
main axes will be used.
|
||||
"""
|
||||
if pad is None:
|
||||
cbook.warn_deprecated(
|
||||
"3.2", message="In a future version, 'pad' will default to "
|
||||
"rcParams['figure.subplot.hspace']. Set pad=0 to keep the "
|
||||
"old behavior.")
|
||||
if pad:
|
||||
if not isinstance(pad, Size._Base):
|
||||
pad = Size.from_any(pad, fraction_ref=self._yref)
|
||||
if pack_start:
|
||||
self._vertical.insert(0, pad)
|
||||
self._yrefindex += 1
|
||||
else:
|
||||
self._vertical.append(pad)
|
||||
if not isinstance(size, Size._Base):
|
||||
size = Size.from_any(size, fraction_ref=self._yref)
|
||||
if pack_start:
|
||||
self._vertical.insert(0, size)
|
||||
self._yrefindex += 1
|
||||
locator = self.new_locator(nx=self._xrefindex, ny=0)
|
||||
else:
|
||||
self._vertical.append(size)
|
||||
locator = self.new_locator(
|
||||
nx=self._xrefindex, ny=len(self._vertical)-1)
|
||||
ax = self._get_new_axes(**kwargs)
|
||||
ax.set_axes_locator(locator)
|
||||
return ax
|
||||
|
||||
def append_axes(self, position, size, pad=None, add_to_figure=True,
|
||||
**kwargs):
|
||||
"""
|
||||
Create an axes at the given *position* with the same height
|
||||
(or width) of the main axes.
|
||||
|
||||
*position*
|
||||
["left"|"right"|"bottom"|"top"]
|
||||
|
||||
*size* and *pad* should be axes_grid.axes_size compatible.
|
||||
"""
|
||||
if position == "left":
|
||||
ax = self.new_horizontal(size, pad, pack_start=True, **kwargs)
|
||||
elif position == "right":
|
||||
ax = self.new_horizontal(size, pad, pack_start=False, **kwargs)
|
||||
elif position == "bottom":
|
||||
ax = self.new_vertical(size, pad, pack_start=True, **kwargs)
|
||||
elif position == "top":
|
||||
ax = self.new_vertical(size, pad, pack_start=False, **kwargs)
|
||||
else:
|
||||
cbook._check_in_list(["left", "right", "bottom", "top"],
|
||||
position=position)
|
||||
if add_to_figure:
|
||||
self._fig.add_axes(ax)
|
||||
return ax
|
||||
|
||||
def get_aspect(self):
|
||||
if self._aspect is None:
|
||||
aspect = self._axes.get_aspect()
|
||||
if aspect == "auto":
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
return self._aspect
|
||||
|
||||
def get_position(self):
|
||||
if self._pos is None:
|
||||
bbox = self._axes.get_position(original=True)
|
||||
return bbox.bounds
|
||||
else:
|
||||
return self._pos
|
||||
|
||||
def get_anchor(self):
|
||||
if self._anchor is None:
|
||||
return self._axes.get_anchor()
|
||||
else:
|
||||
return self._anchor
|
||||
|
||||
def get_subplotspec(self):
|
||||
if hasattr(self._axes, "get_subplotspec"):
|
||||
return self._axes.get_subplotspec()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class HBoxDivider(SubplotDivider):
|
||||
|
||||
@staticmethod
|
||||
def _determine_karray(equivalent_sizes, appended_sizes,
|
||||
max_equivalent_size,
|
||||
total_appended_size):
|
||||
|
||||
n = len(equivalent_sizes)
|
||||
eq_rs, eq_as = np.asarray(equivalent_sizes).T
|
||||
ap_rs, ap_as = np.asarray(appended_sizes).T
|
||||
A = np.zeros((n + 1, n + 1))
|
||||
B = np.zeros(n + 1)
|
||||
np.fill_diagonal(A[:n, :n], eq_rs)
|
||||
A[:n, -1] = -1
|
||||
A[-1, :-1] = ap_rs
|
||||
B[:n] = -eq_as
|
||||
B[-1] = total_appended_size - sum(ap_as)
|
||||
|
||||
karray_H = np.linalg.solve(A, B) # A @ K = B
|
||||
karray = karray_H[:-1]
|
||||
H = karray_H[-1]
|
||||
|
||||
if H > max_equivalent_size:
|
||||
karray = (max_equivalent_size - eq_as) / eq_rs
|
||||
return karray
|
||||
|
||||
@staticmethod
|
||||
def _calc_offsets(appended_sizes, karray):
|
||||
offsets = [0.]
|
||||
for (r, a), k in zip(appended_sizes, karray):
|
||||
offsets.append(offsets[-1] + r*k + a)
|
||||
return offsets
|
||||
|
||||
def new_locator(self, nx, nx1=None):
|
||||
"""
|
||||
Create a new `~mpl_toolkits.axes_grid.axes_divider.AxesLocator` for
|
||||
the specified cell.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nx, nx1 : int
|
||||
Integers specifying the column-position of the
|
||||
cell. When *nx1* is None, a single *nx*-th column is
|
||||
specified. Otherwise location of columns spanning between *nx*
|
||||
to *nx1* (but excluding *nx1*-th column) is specified.
|
||||
ny, ny1 : int
|
||||
Same as *nx* and *nx1*, but for row positions.
|
||||
"""
|
||||
return AxesLocator(self, nx, 0, nx1, None)
|
||||
|
||||
def _locate(self, x, y, w, h,
|
||||
y_equivalent_sizes, x_appended_sizes,
|
||||
figW, figH):
|
||||
equivalent_sizes = y_equivalent_sizes
|
||||
appended_sizes = x_appended_sizes
|
||||
|
||||
max_equivalent_size = figH * h
|
||||
total_appended_size = figW * w
|
||||
karray = self._determine_karray(equivalent_sizes, appended_sizes,
|
||||
max_equivalent_size,
|
||||
total_appended_size)
|
||||
|
||||
ox = self._calc_offsets(appended_sizes, karray)
|
||||
|
||||
ww = (ox[-1] - ox[0]) / figW
|
||||
ref_h = equivalent_sizes[0]
|
||||
hh = (karray[0]*ref_h[0] + ref_h[1]) / figH
|
||||
pb = mtransforms.Bbox.from_bounds(x, y, w, h)
|
||||
pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
|
||||
pb1_anchored = pb1.anchored(self.get_anchor(), pb)
|
||||
x0, y0 = pb1_anchored.x0, pb1_anchored.y0
|
||||
|
||||
return x0, y0, ox, hh
|
||||
|
||||
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
axes_divider : AxesDivider
|
||||
nx, nx1 : int
|
||||
Integers specifying the column-position of the
|
||||
cell. When *nx1* is None, a single *nx*-th column is
|
||||
specified. Otherwise location of columns spanning between *nx*
|
||||
to *nx1* (but excluding *nx1*-th column) is specified.
|
||||
ny, ny1 : int
|
||||
Same as *nx* and *nx1*, but for row positions.
|
||||
axes
|
||||
renderer
|
||||
"""
|
||||
|
||||
figW, figH = self._fig.get_size_inches()
|
||||
x, y, w, h = self.get_position_runtime(axes, renderer)
|
||||
|
||||
y_equivalent_sizes = self.get_vertical_sizes(renderer)
|
||||
x_appended_sizes = self.get_horizontal_sizes(renderer)
|
||||
x0, y0, ox, hh = self._locate(x, y, w, h,
|
||||
y_equivalent_sizes, x_appended_sizes,
|
||||
figW, figH)
|
||||
if nx1 is None:
|
||||
nx1 = nx + 1
|
||||
|
||||
x1, w1 = x0 + ox[nx] / figW, (ox[nx1] - ox[nx]) / figW
|
||||
y1, h1 = y0, hh
|
||||
|
||||
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
|
||||
|
||||
|
||||
class VBoxDivider(HBoxDivider):
|
||||
"""
|
||||
The Divider class whose rectangle area is specified as a subplot geometry.
|
||||
"""
|
||||
|
||||
def new_locator(self, ny, ny1=None):
|
||||
"""
|
||||
Create a new `~mpl_toolkits.axes_grid.axes_divider.AxesLocator` for
|
||||
the specified cell.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ny, ny1 : int
|
||||
Integers specifying the row-position of the
|
||||
cell. When *ny1* is None, a single *ny*-th row is
|
||||
specified. Otherwise location of rows spanning between *ny*
|
||||
to *ny1* (but excluding *ny1*-th row) is specified.
|
||||
"""
|
||||
return AxesLocator(self, 0, ny, None, ny1)
|
||||
|
||||
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
axes_divider : AxesDivider
|
||||
nx, nx1 : int
|
||||
Integers specifying the column-position of the
|
||||
cell. When *nx1* is None, a single *nx*-th column is
|
||||
specified. Otherwise location of columns spanning between *nx*
|
||||
to *nx1* (but excluding *nx1*-th column) is specified.
|
||||
ny, ny1 : int
|
||||
Same as *nx* and *nx1*, but for row positions.
|
||||
axes
|
||||
renderer
|
||||
"""
|
||||
|
||||
figW, figH = self._fig.get_size_inches()
|
||||
x, y, w, h = self.get_position_runtime(axes, renderer)
|
||||
|
||||
x_equivalent_sizes = self.get_horizontal_sizes(renderer)
|
||||
y_appended_sizes = self.get_vertical_sizes(renderer)
|
||||
|
||||
y0, x0, oy, ww = self._locate(y, x, h, w,
|
||||
x_equivalent_sizes, y_appended_sizes,
|
||||
figH, figW)
|
||||
if ny1 is None:
|
||||
ny1 = ny + 1
|
||||
|
||||
x1, w1 = x0, ww
|
||||
y1, h1 = y0 + oy[ny] / figH, (oy[ny1] - oy[ny]) / figH
|
||||
|
||||
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
|
||||
|
||||
|
||||
def make_axes_locatable(axes):
|
||||
divider = AxesDivider(axes)
|
||||
locator = divider.new_locator(nx=0, ny=0)
|
||||
axes.set_axes_locator(locator)
|
||||
|
||||
return divider
|
||||
|
||||
|
||||
def make_axes_area_auto_adjustable(ax,
|
||||
use_axes=None, pad=0.1,
|
||||
adjust_dirs=None):
|
||||
if adjust_dirs is None:
|
||||
adjust_dirs = ["left", "right", "bottom", "top"]
|
||||
divider = make_axes_locatable(ax)
|
||||
|
||||
if use_axes is None:
|
||||
use_axes = ax
|
||||
|
||||
divider.add_auto_adjustable_area(use_axes=use_axes, pad=pad,
|
||||
adjust_dirs=adjust_dirs)
|
624
venv/Lib/site-packages/mpl_toolkits/axes_grid1/axes_grid.py
Normal file
624
venv/Lib/site-packages/mpl_toolkits/axes_grid1/axes_grid.py
Normal file
|
@ -0,0 +1,624 @@
|
|||
from numbers import Number
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
|
||||
import matplotlib as mpl
|
||||
from matplotlib import cbook
|
||||
import matplotlib.ticker as ticker
|
||||
from matplotlib.gridspec import SubplotSpec
|
||||
|
||||
from .axes_divider import Size, SubplotDivider, Divider
|
||||
from .mpl_axes import Axes
|
||||
|
||||
|
||||
def _tick_only(ax, bottom_on, left_on):
|
||||
bottom_off = not bottom_on
|
||||
left_off = not left_on
|
||||
ax.axis["bottom"].toggle(ticklabels=bottom_off, label=bottom_off)
|
||||
ax.axis["left"].toggle(ticklabels=left_off, label=left_off)
|
||||
|
||||
|
||||
class CbarAxesBase:
|
||||
def __init__(self, *args, orientation, **kwargs):
|
||||
self.orientation = orientation
|
||||
self._default_label_on = True
|
||||
self._locator = None # deprecated.
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@cbook._rename_parameter("3.2", "locator", "ticks")
|
||||
def colorbar(self, mappable, *, ticks=None, **kwargs):
|
||||
|
||||
if self.orientation in ["top", "bottom"]:
|
||||
orientation = "horizontal"
|
||||
else:
|
||||
orientation = "vertical"
|
||||
|
||||
if mpl.rcParams["mpl_toolkits.legacy_colorbar"]:
|
||||
cbook.warn_deprecated(
|
||||
"3.2", message="Since %(since)s, mpl_toolkits's own colorbar "
|
||||
"implementation is deprecated; it will be removed "
|
||||
"%(removal)s. Set the 'mpl_toolkits.legacy_colorbar' rcParam "
|
||||
"to False to use Matplotlib's default colorbar implementation "
|
||||
"and suppress this deprecation warning.")
|
||||
if ticks is None:
|
||||
ticks = ticker.MaxNLocator(5) # For backcompat.
|
||||
from .colorbar import Colorbar
|
||||
cb = Colorbar(
|
||||
self, mappable, orientation=orientation, ticks=ticks, **kwargs)
|
||||
self._cbid = mappable.callbacksSM.connect(
|
||||
'changed', cb.update_normal)
|
||||
mappable.colorbar = cb
|
||||
self._locator = cb.cbar_axis.get_major_locator()
|
||||
else:
|
||||
cb = mpl.colorbar.colorbar_factory(
|
||||
self, mappable, orientation=orientation, ticks=ticks, **kwargs)
|
||||
self._cbid = mappable.colorbar_cid # deprecated.
|
||||
self._locator = cb.locator # deprecated.
|
||||
|
||||
self._config_axes()
|
||||
return cb
|
||||
|
||||
cbid = cbook._deprecate_privatize_attribute(
|
||||
"3.3", alternative="mappable.colorbar_cid")
|
||||
locator = cbook._deprecate_privatize_attribute(
|
||||
"3.3", alternative=".colorbar().locator")
|
||||
|
||||
def _config_axes(self):
|
||||
"""Make an axes patch and outline."""
|
||||
ax = self
|
||||
ax.set_navigate(False)
|
||||
ax.axis[:].toggle(all=False)
|
||||
b = self._default_label_on
|
||||
ax.axis[self.orientation].toggle(all=b)
|
||||
|
||||
def toggle_label(self, b):
|
||||
self._default_label_on = b
|
||||
axis = self.axis[self.orientation]
|
||||
axis.toggle(ticklabels=b, label=b)
|
||||
|
||||
def cla(self):
|
||||
super().cla()
|
||||
self._config_axes()
|
||||
|
||||
|
||||
class CbarAxes(CbarAxesBase, Axes):
|
||||
pass
|
||||
|
||||
|
||||
class Grid:
|
||||
"""
|
||||
A grid of Axes.
|
||||
|
||||
In Matplotlib, the axes location (and size) is specified in normalized
|
||||
figure coordinates. This may not be ideal for images that needs to be
|
||||
displayed with a given aspect ratio; for example, it is difficult to
|
||||
display multiple images of a same size with some fixed padding between
|
||||
them. AxesGrid can be used in such case.
|
||||
"""
|
||||
|
||||
_defaultAxesClass = Axes
|
||||
|
||||
@cbook._delete_parameter("3.3", "add_all")
|
||||
def __init__(self, fig,
|
||||
rect,
|
||||
nrows_ncols,
|
||||
ngrids=None,
|
||||
direction="row",
|
||||
axes_pad=0.02,
|
||||
add_all=True,
|
||||
share_all=False,
|
||||
share_x=True,
|
||||
share_y=True,
|
||||
label_mode="L",
|
||||
axes_class=None,
|
||||
*,
|
||||
aspect=False,
|
||||
):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
fig : `.Figure`
|
||||
The parent figure.
|
||||
rect : (float, float, float, float) or int
|
||||
The axes position, as a ``(left, bottom, width, height)`` tuple or
|
||||
as a three-digit subplot position code (e.g., "121").
|
||||
nrows_ncols : (int, int)
|
||||
Number of rows and columns in the grid.
|
||||
ngrids : int or None, default: None
|
||||
If not None, only the first *ngrids* axes in the grid are created.
|
||||
direction : {"row", "column"}, default: "row"
|
||||
Whether axes are created in row-major ("row by row") or
|
||||
column-major order ("column by column").
|
||||
axes_pad : float or (float, float), default: 0.02
|
||||
Padding or (horizontal padding, vertical padding) between axes, in
|
||||
inches.
|
||||
add_all : bool, default: True
|
||||
Whether to add the axes to the figure using `.Figure.add_axes`.
|
||||
This parameter is deprecated.
|
||||
share_all : bool, default: False
|
||||
Whether all axes share their x- and y-axis. Overrides *share_x*
|
||||
and *share_y*.
|
||||
share_x : bool, default: True
|
||||
Whether all axes of a column share their x-axis.
|
||||
share_y : bool, default: True
|
||||
Whether all axes of a row share their y-axis.
|
||||
label_mode : {"L", "1", "all"}, default: "L"
|
||||
Determines which axes will get tick labels:
|
||||
|
||||
- "L": All axes on the left column get vertical tick labels;
|
||||
all axes on the bottom row get horizontal tick labels.
|
||||
- "1": Only the bottom left axes is labelled.
|
||||
- "all": all axes are labelled.
|
||||
|
||||
axes_class : subclass of `matplotlib.axes.Axes`, default: None
|
||||
aspect : bool, default: False
|
||||
Whether the axes aspect ratio follows the aspect ratio of the data
|
||||
limits.
|
||||
"""
|
||||
self._nrows, self._ncols = nrows_ncols
|
||||
|
||||
if ngrids is None:
|
||||
ngrids = self._nrows * self._ncols
|
||||
else:
|
||||
if not 0 < ngrids <= self._nrows * self._ncols:
|
||||
raise Exception("")
|
||||
|
||||
self.ngrids = ngrids
|
||||
|
||||
self._horiz_pad_size, self._vert_pad_size = map(
|
||||
Size.Fixed, np.broadcast_to(axes_pad, 2))
|
||||
|
||||
cbook._check_in_list(["column", "row"], direction=direction)
|
||||
self._direction = direction
|
||||
|
||||
if axes_class is None:
|
||||
axes_class = self._defaultAxesClass
|
||||
elif isinstance(axes_class, (list, tuple)):
|
||||
cls, kwargs = axes_class
|
||||
axes_class = functools.partial(cls, **kwargs)
|
||||
|
||||
kw = dict(horizontal=[], vertical=[], aspect=aspect)
|
||||
if isinstance(rect, (str, Number, SubplotSpec)):
|
||||
self._divider = SubplotDivider(fig, rect, **kw)
|
||||
elif len(rect) == 3:
|
||||
self._divider = SubplotDivider(fig, *rect, **kw)
|
||||
elif len(rect) == 4:
|
||||
self._divider = Divider(fig, rect, **kw)
|
||||
else:
|
||||
raise Exception("")
|
||||
|
||||
rect = self._divider.get_position()
|
||||
|
||||
axes_array = np.full((self._nrows, self._ncols), None, dtype=object)
|
||||
for i in range(self.ngrids):
|
||||
col, row = self._get_col_row(i)
|
||||
if share_all:
|
||||
sharex = sharey = axes_array[0, 0]
|
||||
else:
|
||||
sharex = axes_array[0, col] if share_x else None
|
||||
sharey = axes_array[row, 0] if share_y else None
|
||||
axes_array[row, col] = axes_class(
|
||||
fig, rect, sharex=sharex, sharey=sharey)
|
||||
self.axes_all = axes_array.ravel().tolist()
|
||||
self.axes_column = axes_array.T.tolist()
|
||||
self.axes_row = axes_array.tolist()
|
||||
self.axes_llc = self.axes_column[0][-1]
|
||||
|
||||
self._init_locators()
|
||||
|
||||
if add_all:
|
||||
for ax in self.axes_all:
|
||||
fig.add_axes(ax)
|
||||
|
||||
self.set_label_mode(label_mode)
|
||||
|
||||
def _init_locators(self):
|
||||
|
||||
h = []
|
||||
h_ax_pos = []
|
||||
for _ in range(self._ncols):
|
||||
if h:
|
||||
h.append(self._horiz_pad_size)
|
||||
h_ax_pos.append(len(h))
|
||||
sz = Size.Scaled(1)
|
||||
h.append(sz)
|
||||
|
||||
v = []
|
||||
v_ax_pos = []
|
||||
for _ in range(self._nrows):
|
||||
if v:
|
||||
v.append(self._vert_pad_size)
|
||||
v_ax_pos.append(len(v))
|
||||
sz = Size.Scaled(1)
|
||||
v.append(sz)
|
||||
|
||||
for i in range(self.ngrids):
|
||||
col, row = self._get_col_row(i)
|
||||
locator = self._divider.new_locator(
|
||||
nx=h_ax_pos[col], ny=v_ax_pos[self._nrows - 1 - row])
|
||||
self.axes_all[i].set_axes_locator(locator)
|
||||
|
||||
self._divider.set_horizontal(h)
|
||||
self._divider.set_vertical(v)
|
||||
|
||||
def _get_col_row(self, n):
|
||||
if self._direction == "column":
|
||||
col, row = divmod(n, self._nrows)
|
||||
else:
|
||||
row, col = divmod(n, self._ncols)
|
||||
|
||||
return col, row
|
||||
|
||||
# Good to propagate __len__ if we have __getitem__
|
||||
def __len__(self):
|
||||
return len(self.axes_all)
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.axes_all[i]
|
||||
|
||||
def get_geometry(self):
|
||||
"""
|
||||
Return the number of rows and columns of the grid as (nrows, ncols).
|
||||
"""
|
||||
return self._nrows, self._ncols
|
||||
|
||||
def set_axes_pad(self, axes_pad):
|
||||
"""
|
||||
Set the padding between the axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
axes_pad : (float, float)
|
||||
The padding (horizontal pad, vertical pad) in inches.
|
||||
"""
|
||||
self._horiz_pad_size.fixed_size = axes_pad[0]
|
||||
self._vert_pad_size.fixed_size = axes_pad[1]
|
||||
|
||||
def get_axes_pad(self):
|
||||
"""
|
||||
Return the axes padding.
|
||||
|
||||
Returns
|
||||
-------
|
||||
hpad, vpad
|
||||
Padding (horizontal pad, vertical pad) in inches.
|
||||
"""
|
||||
return (self._horiz_pad_size.fixed_size,
|
||||
self._vert_pad_size.fixed_size)
|
||||
|
||||
def set_aspect(self, aspect):
|
||||
"""Set the aspect of the SubplotDivider."""
|
||||
self._divider.set_aspect(aspect)
|
||||
|
||||
def get_aspect(self):
|
||||
"""Return the aspect of the SubplotDivider."""
|
||||
return self._divider.get_aspect()
|
||||
|
||||
def set_label_mode(self, mode):
|
||||
"""
|
||||
Define which axes have tick labels.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mode : {"L", "1", "all"}
|
||||
The label mode:
|
||||
|
||||
- "L": All axes on the left column get vertical tick labels;
|
||||
all axes on the bottom row get horizontal tick labels.
|
||||
- "1": Only the bottom left axes is labelled.
|
||||
- "all": all axes are labelled.
|
||||
"""
|
||||
if mode == "all":
|
||||
for ax in self.axes_all:
|
||||
_tick_only(ax, False, False)
|
||||
elif mode == "L":
|
||||
# left-most axes
|
||||
for ax in self.axes_column[0][:-1]:
|
||||
_tick_only(ax, bottom_on=True, left_on=False)
|
||||
# lower-left axes
|
||||
ax = self.axes_column[0][-1]
|
||||
_tick_only(ax, bottom_on=False, left_on=False)
|
||||
|
||||
for col in self.axes_column[1:]:
|
||||
# axes with no labels
|
||||
for ax in col[:-1]:
|
||||
_tick_only(ax, bottom_on=True, left_on=True)
|
||||
|
||||
# bottom
|
||||
ax = col[-1]
|
||||
_tick_only(ax, bottom_on=False, left_on=True)
|
||||
|
||||
elif mode == "1":
|
||||
for ax in self.axes_all:
|
||||
_tick_only(ax, bottom_on=True, left_on=True)
|
||||
|
||||
ax = self.axes_llc
|
||||
_tick_only(ax, bottom_on=False, left_on=False)
|
||||
|
||||
def get_divider(self):
|
||||
return self._divider
|
||||
|
||||
def set_axes_locator(self, locator):
|
||||
self._divider.set_locator(locator)
|
||||
|
||||
def get_axes_locator(self):
|
||||
return self._divider.get_locator()
|
||||
|
||||
def get_vsize_hsize(self):
|
||||
return self._divider.get_vsize_hsize()
|
||||
|
||||
|
||||
class ImageGrid(Grid):
|
||||
# docstring inherited
|
||||
|
||||
_defaultCbarAxesClass = CbarAxes
|
||||
|
||||
@cbook._delete_parameter("3.3", "add_all")
|
||||
def __init__(self, fig,
|
||||
rect,
|
||||
nrows_ncols,
|
||||
ngrids=None,
|
||||
direction="row",
|
||||
axes_pad=0.02,
|
||||
add_all=True,
|
||||
share_all=False,
|
||||
aspect=True,
|
||||
label_mode="L",
|
||||
cbar_mode=None,
|
||||
cbar_location="right",
|
||||
cbar_pad=None,
|
||||
cbar_size="5%",
|
||||
cbar_set_cax=True,
|
||||
axes_class=None,
|
||||
):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
fig : `.Figure`
|
||||
The parent figure.
|
||||
rect : (float, float, float, float) or int
|
||||
The axes position, as a ``(left, bottom, width, height)`` tuple or
|
||||
as a three-digit subplot position code (e.g., "121").
|
||||
nrows_ncols : (int, int)
|
||||
Number of rows and columns in the grid.
|
||||
ngrids : int or None, default: None
|
||||
If not None, only the first *ngrids* axes in the grid are created.
|
||||
direction : {"row", "column"}, default: "row"
|
||||
Whether axes are created in row-major ("row by row") or
|
||||
column-major order ("column by column"). This also affects the
|
||||
order in which axes are accessed using indexing (``grid[index]``).
|
||||
axes_pad : float or (float, float), default: 0.02in
|
||||
Padding or (horizontal padding, vertical padding) between axes, in
|
||||
inches.
|
||||
add_all : bool, default: True
|
||||
Whether to add the axes to the figure using `.Figure.add_axes`.
|
||||
This parameter is deprecated.
|
||||
share_all : bool, default: False
|
||||
Whether all axes share their x- and y-axis.
|
||||
aspect : bool, default: True
|
||||
Whether the axes aspect ratio follows the aspect ratio of the data
|
||||
limits.
|
||||
label_mode : {"L", "1", "all"}, default: "L"
|
||||
Determines which axes will get tick labels:
|
||||
|
||||
- "L": All axes on the left column get vertical tick labels;
|
||||
all axes on the bottom row get horizontal tick labels.
|
||||
- "1": Only the bottom left axes is labelled.
|
||||
- "all": all axes are labelled.
|
||||
|
||||
cbar_mode : {"each", "single", "edge", None}, default: None
|
||||
Whether to create a colorbar for "each" axes, a "single" colorbar
|
||||
for the entire grid, colorbars only for axes on the "edge"
|
||||
determined by *cbar_location*, or no colorbars. The colorbars are
|
||||
stored in the :attr:`cbar_axes` attribute.
|
||||
cbar_location : {"left", "right", "bottom", "top"}, default: "right"
|
||||
cbar_pad : float, default: None
|
||||
Padding between the image axes and the colorbar axes.
|
||||
cbar_size : size specification (see `.Size.from_any`), default: "5%"
|
||||
Colorbar size.
|
||||
cbar_set_cax : bool, default: True
|
||||
If True, each axes in the grid has a *cax* attribute that is bound
|
||||
to associated *cbar_axes*.
|
||||
axes_class : subclass of `matplotlib.axes.Axes`, default: None
|
||||
"""
|
||||
self._colorbar_mode = cbar_mode
|
||||
self._colorbar_location = cbar_location
|
||||
self._colorbar_pad = cbar_pad
|
||||
self._colorbar_size = cbar_size
|
||||
# The colorbar axes are created in _init_locators().
|
||||
|
||||
if add_all:
|
||||
super().__init__(
|
||||
fig, rect, nrows_ncols, ngrids,
|
||||
direction=direction, axes_pad=axes_pad,
|
||||
share_all=share_all, share_x=True, share_y=True, aspect=aspect,
|
||||
label_mode=label_mode, axes_class=axes_class)
|
||||
else: # Only show deprecation in that case.
|
||||
super().__init__(
|
||||
fig, rect, nrows_ncols, ngrids,
|
||||
direction=direction, axes_pad=axes_pad, add_all=add_all,
|
||||
share_all=share_all, share_x=True, share_y=True, aspect=aspect,
|
||||
label_mode=label_mode, axes_class=axes_class)
|
||||
|
||||
if add_all:
|
||||
for ax in self.cbar_axes:
|
||||
fig.add_axes(ax)
|
||||
|
||||
if cbar_set_cax:
|
||||
if self._colorbar_mode == "single":
|
||||
for ax in self.axes_all:
|
||||
ax.cax = self.cbar_axes[0]
|
||||
elif self._colorbar_mode == "edge":
|
||||
for index, ax in enumerate(self.axes_all):
|
||||
col, row = self._get_col_row(index)
|
||||
if self._colorbar_location in ("left", "right"):
|
||||
ax.cax = self.cbar_axes[row]
|
||||
else:
|
||||
ax.cax = self.cbar_axes[col]
|
||||
else:
|
||||
for ax, cax in zip(self.axes_all, self.cbar_axes):
|
||||
ax.cax = cax
|
||||
|
||||
def _init_locators(self):
|
||||
# Slightly abusing this method to inject colorbar creation into init.
|
||||
|
||||
if self._colorbar_pad is None:
|
||||
# horizontal or vertical arrangement?
|
||||
if self._colorbar_location in ("left", "right"):
|
||||
self._colorbar_pad = self._horiz_pad_size.fixed_size
|
||||
else:
|
||||
self._colorbar_pad = self._vert_pad_size.fixed_size
|
||||
self.cbar_axes = [
|
||||
self._defaultCbarAxesClass(
|
||||
self.axes_all[0].figure, self._divider.get_position(),
|
||||
orientation=self._colorbar_location)
|
||||
for _ in range(self.ngrids)]
|
||||
|
||||
cb_mode = self._colorbar_mode
|
||||
cb_location = self._colorbar_location
|
||||
|
||||
h = []
|
||||
v = []
|
||||
|
||||
h_ax_pos = []
|
||||
h_cb_pos = []
|
||||
if cb_mode == "single" and cb_location in ("left", "bottom"):
|
||||
if cb_location == "left":
|
||||
sz = self._nrows * Size.AxesX(self.axes_llc)
|
||||
h.append(Size.from_any(self._colorbar_size, sz))
|
||||
h.append(Size.from_any(self._colorbar_pad, sz))
|
||||
locator = self._divider.new_locator(nx=0, ny=0, ny1=-1)
|
||||
elif cb_location == "bottom":
|
||||
sz = self._ncols * Size.AxesY(self.axes_llc)
|
||||
v.append(Size.from_any(self._colorbar_size, sz))
|
||||
v.append(Size.from_any(self._colorbar_pad, sz))
|
||||
locator = self._divider.new_locator(nx=0, nx1=-1, ny=0)
|
||||
for i in range(self.ngrids):
|
||||
self.cbar_axes[i].set_visible(False)
|
||||
self.cbar_axes[0].set_axes_locator(locator)
|
||||
self.cbar_axes[0].set_visible(True)
|
||||
|
||||
for col, ax in enumerate(self.axes_row[0]):
|
||||
if h:
|
||||
h.append(self._horiz_pad_size)
|
||||
|
||||
if ax:
|
||||
sz = Size.AxesX(ax, aspect="axes", ref_ax=self.axes_all[0])
|
||||
else:
|
||||
sz = Size.AxesX(self.axes_all[0],
|
||||
aspect="axes", ref_ax=self.axes_all[0])
|
||||
|
||||
if (cb_location == "left"
|
||||
and (cb_mode == "each"
|
||||
or (cb_mode == "edge" and col == 0))):
|
||||
h_cb_pos.append(len(h))
|
||||
h.append(Size.from_any(self._colorbar_size, sz))
|
||||
h.append(Size.from_any(self._colorbar_pad, sz))
|
||||
|
||||
h_ax_pos.append(len(h))
|
||||
h.append(sz)
|
||||
|
||||
if (cb_location == "right"
|
||||
and (cb_mode == "each"
|
||||
or (cb_mode == "edge" and col == self._ncols - 1))):
|
||||
h.append(Size.from_any(self._colorbar_pad, sz))
|
||||
h_cb_pos.append(len(h))
|
||||
h.append(Size.from_any(self._colorbar_size, sz))
|
||||
|
||||
v_ax_pos = []
|
||||
v_cb_pos = []
|
||||
for row, ax in enumerate(self.axes_column[0][::-1]):
|
||||
if v:
|
||||
v.append(self._vert_pad_size)
|
||||
|
||||
if ax:
|
||||
sz = Size.AxesY(ax, aspect="axes", ref_ax=self.axes_all[0])
|
||||
else:
|
||||
sz = Size.AxesY(self.axes_all[0],
|
||||
aspect="axes", ref_ax=self.axes_all[0])
|
||||
|
||||
if (cb_location == "bottom"
|
||||
and (cb_mode == "each"
|
||||
or (cb_mode == "edge" and row == 0))):
|
||||
v_cb_pos.append(len(v))
|
||||
v.append(Size.from_any(self._colorbar_size, sz))
|
||||
v.append(Size.from_any(self._colorbar_pad, sz))
|
||||
|
||||
v_ax_pos.append(len(v))
|
||||
v.append(sz)
|
||||
|
||||
if (cb_location == "top"
|
||||
and (cb_mode == "each"
|
||||
or (cb_mode == "edge" and row == self._nrows - 1))):
|
||||
v.append(Size.from_any(self._colorbar_pad, sz))
|
||||
v_cb_pos.append(len(v))
|
||||
v.append(Size.from_any(self._colorbar_size, sz))
|
||||
|
||||
for i in range(self.ngrids):
|
||||
col, row = self._get_col_row(i)
|
||||
locator = self._divider.new_locator(nx=h_ax_pos[col],
|
||||
ny=v_ax_pos[self._nrows-1-row])
|
||||
self.axes_all[i].set_axes_locator(locator)
|
||||
|
||||
if cb_mode == "each":
|
||||
if cb_location in ("right", "left"):
|
||||
locator = self._divider.new_locator(
|
||||
nx=h_cb_pos[col], ny=v_ax_pos[self._nrows - 1 - row])
|
||||
|
||||
elif cb_location in ("top", "bottom"):
|
||||
locator = self._divider.new_locator(
|
||||
nx=h_ax_pos[col], ny=v_cb_pos[self._nrows - 1 - row])
|
||||
|
||||
self.cbar_axes[i].set_axes_locator(locator)
|
||||
elif cb_mode == "edge":
|
||||
if (cb_location == "left" and col == 0
|
||||
or cb_location == "right" and col == self._ncols - 1):
|
||||
locator = self._divider.new_locator(
|
||||
nx=h_cb_pos[0], ny=v_ax_pos[self._nrows - 1 - row])
|
||||
self.cbar_axes[row].set_axes_locator(locator)
|
||||
elif (cb_location == "bottom" and row == self._nrows - 1
|
||||
or cb_location == "top" and row == 0):
|
||||
locator = self._divider.new_locator(nx=h_ax_pos[col],
|
||||
ny=v_cb_pos[0])
|
||||
self.cbar_axes[col].set_axes_locator(locator)
|
||||
|
||||
if cb_mode == "single":
|
||||
if cb_location == "right":
|
||||
sz = self._nrows * Size.AxesX(self.axes_llc)
|
||||
h.append(Size.from_any(self._colorbar_pad, sz))
|
||||
h.append(Size.from_any(self._colorbar_size, sz))
|
||||
locator = self._divider.new_locator(nx=-2, ny=0, ny1=-1)
|
||||
elif cb_location == "top":
|
||||
sz = self._ncols * Size.AxesY(self.axes_llc)
|
||||
v.append(Size.from_any(self._colorbar_pad, sz))
|
||||
v.append(Size.from_any(self._colorbar_size, sz))
|
||||
locator = self._divider.new_locator(nx=0, nx1=-1, ny=-2)
|
||||
if cb_location in ("right", "top"):
|
||||
for i in range(self.ngrids):
|
||||
self.cbar_axes[i].set_visible(False)
|
||||
self.cbar_axes[0].set_axes_locator(locator)
|
||||
self.cbar_axes[0].set_visible(True)
|
||||
elif cb_mode == "each":
|
||||
for i in range(self.ngrids):
|
||||
self.cbar_axes[i].set_visible(True)
|
||||
elif cb_mode == "edge":
|
||||
if cb_location in ("right", "left"):
|
||||
count = self._nrows
|
||||
else:
|
||||
count = self._ncols
|
||||
for i in range(count):
|
||||
self.cbar_axes[i].set_visible(True)
|
||||
for j in range(i + 1, self.ngrids):
|
||||
self.cbar_axes[j].set_visible(False)
|
||||
else:
|
||||
for i in range(self.ngrids):
|
||||
self.cbar_axes[i].set_visible(False)
|
||||
self.cbar_axes[i].set_position([1., 1., 0.001, 0.001],
|
||||
which="active")
|
||||
|
||||
self._divider.set_horizontal(h)
|
||||
self._divider.set_vertical(v)
|
||||
|
||||
|
||||
AxesGrid = ImageGrid
|
171
venv/Lib/site-packages/mpl_toolkits/axes_grid1/axes_rgb.py
Normal file
171
venv/Lib/site-packages/mpl_toolkits/axes_grid1/axes_rgb.py
Normal file
|
@ -0,0 +1,171 @@
|
|||
import numpy as np
|
||||
|
||||
from matplotlib import cbook
|
||||
from .axes_divider import make_axes_locatable, Size
|
||||
from .mpl_axes import Axes
|
||||
|
||||
|
||||
@cbook._delete_parameter("3.3", "add_all")
|
||||
def make_rgb_axes(ax, pad=0.01, axes_class=None, add_all=True, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
pad : float
|
||||
Fraction of the axes height.
|
||||
"""
|
||||
|
||||
divider = make_axes_locatable(ax)
|
||||
|
||||
pad_size = pad * Size.AxesY(ax)
|
||||
|
||||
xsize = ((1-2*pad)/3) * Size.AxesX(ax)
|
||||
ysize = ((1-2*pad)/3) * Size.AxesY(ax)
|
||||
|
||||
divider.set_horizontal([Size.AxesX(ax), pad_size, xsize])
|
||||
divider.set_vertical([ysize, pad_size, ysize, pad_size, ysize])
|
||||
|
||||
ax.set_axes_locator(divider.new_locator(0, 0, ny1=-1))
|
||||
|
||||
ax_rgb = []
|
||||
if axes_class is None:
|
||||
try:
|
||||
axes_class = ax._axes_class
|
||||
except AttributeError:
|
||||
axes_class = type(ax)
|
||||
|
||||
for ny in [4, 2, 0]:
|
||||
ax1 = axes_class(ax.get_figure(), ax.get_position(original=True),
|
||||
sharex=ax, sharey=ax, **kwargs)
|
||||
locator = divider.new_locator(nx=2, ny=ny)
|
||||
ax1.set_axes_locator(locator)
|
||||
for t in ax1.yaxis.get_ticklabels() + ax1.xaxis.get_ticklabels():
|
||||
t.set_visible(False)
|
||||
try:
|
||||
for axis in ax1.axis.values():
|
||||
axis.major_ticklabels.set_visible(False)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
ax_rgb.append(ax1)
|
||||
|
||||
if add_all:
|
||||
fig = ax.get_figure()
|
||||
for ax1 in ax_rgb:
|
||||
fig.add_axes(ax1)
|
||||
|
||||
return ax_rgb
|
||||
|
||||
|
||||
@cbook.deprecated("3.3", alternative="ax.imshow(np.dstack([r, g, b]))")
|
||||
def imshow_rgb(ax, r, g, b, **kwargs):
|
||||
return ax.imshow(np.dstack([r, g, b]), **kwargs)
|
||||
|
||||
|
||||
class RGBAxes:
|
||||
"""
|
||||
4-panel imshow (RGB, R, G, B).
|
||||
|
||||
Layout:
|
||||
+---------------+-----+
|
||||
| | R |
|
||||
+ +-----+
|
||||
| RGB | G |
|
||||
+ +-----+
|
||||
| | B |
|
||||
+---------------+-----+
|
||||
|
||||
Subclasses can override the ``_defaultAxesClass`` attribute.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
RGB : ``_defaultAxesClass``
|
||||
The axes object for the three-channel imshow.
|
||||
R : ``_defaultAxesClass``
|
||||
The axes object for the red channel imshow.
|
||||
G : ``_defaultAxesClass``
|
||||
The axes object for the green channel imshow.
|
||||
B : ``_defaultAxesClass``
|
||||
The axes object for the blue channel imshow.
|
||||
"""
|
||||
|
||||
_defaultAxesClass = Axes
|
||||
|
||||
@cbook._delete_parameter("3.3", "add_all")
|
||||
def __init__(self, *args, pad=0, add_all=True, **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
pad : float, default: 0
|
||||
fraction of the axes height to put as padding.
|
||||
add_all : bool, default: True
|
||||
Whether to add the {rgb, r, g, b} axes to the figure.
|
||||
This parameter is deprecated.
|
||||
axes_class : matplotlib.axes.Axes
|
||||
|
||||
*args
|
||||
Unpacked into axes_class() init for RGB
|
||||
**kwargs
|
||||
Unpacked into axes_class() init for RGB, R, G, B axes
|
||||
"""
|
||||
axes_class = kwargs.pop("axes_class", self._defaultAxesClass)
|
||||
self.RGB = ax = axes_class(*args, **kwargs)
|
||||
if add_all:
|
||||
ax.get_figure().add_axes(ax)
|
||||
else:
|
||||
kwargs["add_all"] = add_all # only show deprecation in that case
|
||||
self.R, self.G, self.B = make_rgb_axes(
|
||||
ax, pad=pad, axes_class=axes_class, **kwargs)
|
||||
# Set the line color and ticks for the axes.
|
||||
for ax1 in [self.RGB, self.R, self.G, self.B]:
|
||||
ax1.axis[:].line.set_color("w")
|
||||
ax1.axis[:].major_ticks.set_markeredgecolor("w")
|
||||
|
||||
@cbook.deprecated("3.3")
|
||||
def add_RGB_to_figure(self):
|
||||
"""Add red, green and blue axes to the RGB composite's axes figure."""
|
||||
self.RGB.get_figure().add_axes(self.R)
|
||||
self.RGB.get_figure().add_axes(self.G)
|
||||
self.RGB.get_figure().add_axes(self.B)
|
||||
|
||||
def imshow_rgb(self, r, g, b, **kwargs):
|
||||
"""
|
||||
Create the four images {rgb, r, g, b}.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
r : 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(
|
||||
f'Input shapes ({r.shape}, {g.shape}, {b.shape}) do not match')
|
||||
RGB = np.dstack([r, g, b])
|
||||
R = np.zeros_like(RGB)
|
||||
R[:, :, 0] = r
|
||||
G = np.zeros_like(RGB)
|
||||
G[:, :, 1] = g
|
||||
B = np.zeros_like(RGB)
|
||||
B[:, :, 2] = b
|
||||
im_rgb = self.RGB.imshow(RGB, **kwargs)
|
||||
im_r = self.R.imshow(R, **kwargs)
|
||||
im_g = self.G.imshow(G, **kwargs)
|
||||
im_b = self.B.imshow(B, **kwargs)
|
||||
return im_rgb, im_r, im_g, im_b
|
||||
|
||||
|
||||
@cbook.deprecated("3.3", alternative="RGBAxes")
|
||||
class RGBAxesBase(RGBAxes):
|
||||
pass
|
272
venv/Lib/site-packages/mpl_toolkits/axes_grid1/axes_size.py
Normal file
272
venv/Lib/site-packages/mpl_toolkits/axes_grid1/axes_size.py
Normal file
|
@ -0,0 +1,272 @@
|
|||
"""
|
||||
Provides classes of simple units that will be used with AxesDivider
|
||||
class (or others) to determine the size of each axes. The unit
|
||||
classes define `get_size` method that returns a tuple of two floats,
|
||||
meaning relative and absolute sizes, respectively.
|
||||
|
||||
Note that this class is nothing more than a simple tuple of two
|
||||
floats. Take a look at the Divider class to see how these two
|
||||
values are used.
|
||||
"""
|
||||
|
||||
from numbers import Number
|
||||
|
||||
from matplotlib import cbook
|
||||
from matplotlib.axes import Axes
|
||||
|
||||
|
||||
class _Base:
|
||||
|
||||
def __rmul__(self, other):
|
||||
return Fraction(other, self)
|
||||
|
||||
def __add__(self, other):
|
||||
if isinstance(other, _Base):
|
||||
return Add(self, other)
|
||||
else:
|
||||
return Add(self, Fixed(other))
|
||||
|
||||
|
||||
class Add(_Base):
|
||||
def __init__(self, a, b):
|
||||
self._a = a
|
||||
self._b = b
|
||||
|
||||
def get_size(self, renderer):
|
||||
a_rel_size, a_abs_size = self._a.get_size(renderer)
|
||||
b_rel_size, b_abs_size = self._b.get_size(renderer)
|
||||
return a_rel_size + b_rel_size, a_abs_size + b_abs_size
|
||||
|
||||
|
||||
class AddList(_Base):
|
||||
def __init__(self, add_list):
|
||||
self._list = add_list
|
||||
|
||||
def get_size(self, renderer):
|
||||
sum_rel_size = sum([a.get_size(renderer)[0] for a in self._list])
|
||||
sum_abs_size = sum([a.get_size(renderer)[1] for a in self._list])
|
||||
return sum_rel_size, sum_abs_size
|
||||
|
||||
|
||||
class Fixed(_Base):
|
||||
"""
|
||||
Simple fixed size with absolute part = *fixed_size* and relative part = 0.
|
||||
"""
|
||||
|
||||
def __init__(self, fixed_size):
|
||||
cbook._check_isinstance(Number, fixed_size=fixed_size)
|
||||
self.fixed_size = fixed_size
|
||||
|
||||
def get_size(self, renderer):
|
||||
rel_size = 0.
|
||||
abs_size = self.fixed_size
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class Scaled(_Base):
|
||||
"""
|
||||
Simple scaled(?) size with absolute part = 0 and
|
||||
relative part = *scalable_size*.
|
||||
"""
|
||||
|
||||
def __init__(self, scalable_size):
|
||||
self._scalable_size = scalable_size
|
||||
|
||||
def get_size(self, renderer):
|
||||
rel_size = self._scalable_size
|
||||
abs_size = 0.
|
||||
return rel_size, abs_size
|
||||
|
||||
Scalable = Scaled
|
||||
|
||||
|
||||
def _get_axes_aspect(ax):
|
||||
aspect = ax.get_aspect()
|
||||
if aspect == "auto":
|
||||
aspect = 1.
|
||||
return aspect
|
||||
|
||||
|
||||
class AxesX(_Base):
|
||||
"""
|
||||
Scaled size whose relative part corresponds to the data width
|
||||
of the *axes* multiplied by the *aspect*.
|
||||
"""
|
||||
|
||||
def __init__(self, axes, aspect=1., ref_ax=None):
|
||||
self._axes = axes
|
||||
self._aspect = aspect
|
||||
if aspect == "axes" and ref_ax is None:
|
||||
raise ValueError("ref_ax must be set when aspect='axes'")
|
||||
self._ref_ax = ref_ax
|
||||
|
||||
def get_size(self, renderer):
|
||||
l1, l2 = self._axes.get_xlim()
|
||||
if self._aspect == "axes":
|
||||
ref_aspect = _get_axes_aspect(self._ref_ax)
|
||||
aspect = ref_aspect / _get_axes_aspect(self._axes)
|
||||
else:
|
||||
aspect = self._aspect
|
||||
|
||||
rel_size = abs(l2-l1)*aspect
|
||||
abs_size = 0.
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class AxesY(_Base):
|
||||
"""
|
||||
Scaled size whose relative part corresponds to the data height
|
||||
of the *axes* multiplied by the *aspect*.
|
||||
"""
|
||||
|
||||
def __init__(self, axes, aspect=1., ref_ax=None):
|
||||
self._axes = axes
|
||||
self._aspect = aspect
|
||||
if aspect == "axes" and ref_ax is None:
|
||||
raise ValueError("ref_ax must be set when aspect='axes'")
|
||||
self._ref_ax = ref_ax
|
||||
|
||||
def get_size(self, renderer):
|
||||
l1, l2 = self._axes.get_ylim()
|
||||
|
||||
if self._aspect == "axes":
|
||||
ref_aspect = _get_axes_aspect(self._ref_ax)
|
||||
aspect = _get_axes_aspect(self._axes)
|
||||
else:
|
||||
aspect = self._aspect
|
||||
|
||||
rel_size = abs(l2-l1)*aspect
|
||||
abs_size = 0.
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class MaxExtent(_Base):
|
||||
"""
|
||||
Size whose absolute part is either the largest width or the largest height
|
||||
of the given *artist_list*.
|
||||
"""
|
||||
|
||||
def __init__(self, artist_list, w_or_h):
|
||||
self._artist_list = artist_list
|
||||
cbook._check_in_list(["width", "height"], w_or_h=w_or_h)
|
||||
self._w_or_h = w_or_h
|
||||
|
||||
def add_artist(self, a):
|
||||
self._artist_list.append(a)
|
||||
|
||||
def get_size(self, renderer):
|
||||
rel_size = 0.
|
||||
extent_list = [
|
||||
getattr(a.get_window_extent(renderer), self._w_or_h) / a.figure.dpi
|
||||
for a in self._artist_list]
|
||||
abs_size = max(extent_list, default=0)
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class MaxWidth(MaxExtent):
|
||||
"""
|
||||
Size whose absolute part is the largest width of the given *artist_list*.
|
||||
"""
|
||||
|
||||
def __init__(self, artist_list):
|
||||
super().__init__(artist_list, "width")
|
||||
|
||||
|
||||
class MaxHeight(MaxExtent):
|
||||
"""
|
||||
Size whose absolute part is the largest height of the given *artist_list*.
|
||||
"""
|
||||
|
||||
def __init__(self, artist_list):
|
||||
super().__init__(artist_list, "height")
|
||||
|
||||
|
||||
class Fraction(_Base):
|
||||
"""
|
||||
An instance whose size is a *fraction* of the *ref_size*.
|
||||
|
||||
>>> s = Fraction(0.3, AxesX(ax))
|
||||
"""
|
||||
|
||||
def __init__(self, fraction, ref_size):
|
||||
cbook._check_isinstance(Number, fraction=fraction)
|
||||
self._fraction_ref = ref_size
|
||||
self._fraction = fraction
|
||||
|
||||
def get_size(self, renderer):
|
||||
if self._fraction_ref is None:
|
||||
return self._fraction, 0.
|
||||
else:
|
||||
r, a = self._fraction_ref.get_size(renderer)
|
||||
rel_size = r*self._fraction
|
||||
abs_size = a*self._fraction
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class Padded(_Base):
|
||||
"""
|
||||
Return a instance where the absolute part of *size* is
|
||||
increase by the amount of *pad*.
|
||||
"""
|
||||
|
||||
def __init__(self, size, pad):
|
||||
self._size = size
|
||||
self._pad = pad
|
||||
|
||||
def get_size(self, renderer):
|
||||
r, a = self._size.get_size(renderer)
|
||||
rel_size = r
|
||||
abs_size = a + self._pad
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
def from_any(size, fraction_ref=None):
|
||||
"""
|
||||
Create a Fixed unit when the first argument is a float, or a
|
||||
Fraction unit if that is a string that ends with %. The second
|
||||
argument is only meaningful when Fraction unit is created.
|
||||
|
||||
>>> a = Size.from_any(1.2) # => Size.Fixed(1.2)
|
||||
>>> Size.from_any("50%", a) # => Size.Fraction(0.5, a)
|
||||
"""
|
||||
if isinstance(size, Number):
|
||||
return Fixed(size)
|
||||
elif isinstance(size, str):
|
||||
if size[-1] == "%":
|
||||
return Fraction(float(size[:-1]) / 100, fraction_ref)
|
||||
raise ValueError("Unknown format")
|
||||
|
||||
|
||||
class SizeFromFunc(_Base):
|
||||
def __init__(self, func):
|
||||
self._func = func
|
||||
|
||||
def get_size(self, renderer):
|
||||
rel_size = 0.
|
||||
|
||||
bb = self._func(renderer)
|
||||
dpi = renderer.points_to_pixels(72.)
|
||||
abs_size = bb/dpi
|
||||
|
||||
return rel_size, abs_size
|
||||
|
||||
|
||||
class GetExtentHelper:
|
||||
_get_func_map = {
|
||||
"left": lambda self, axes_bbox: axes_bbox.xmin - self.xmin,
|
||||
"right": lambda self, axes_bbox: self.xmax - axes_bbox.xmax,
|
||||
"bottom": lambda self, axes_bbox: axes_bbox.ymin - self.ymin,
|
||||
"top": lambda self, axes_bbox: self.ymax - axes_bbox.ymax,
|
||||
}
|
||||
|
||||
def __init__(self, ax, direction):
|
||||
cbook._check_in_list(self._get_func_map, direction=direction)
|
||||
self._ax_list = [ax] if isinstance(ax, Axes) else ax
|
||||
self._direction = direction
|
||||
|
||||
def __call__(self, renderer):
|
||||
get_func = self._get_func_map[self._direction]
|
||||
vl = [get_func(ax.get_tightbbox(renderer, call_axes_locator=False),
|
||||
ax.bbox)
|
||||
for ax in self._ax_list]
|
||||
return max(vl)
|
804
venv/Lib/site-packages/mpl_toolkits/axes_grid1/colorbar.py
Normal file
804
venv/Lib/site-packages/mpl_toolkits/axes_grid1/colorbar.py
Normal file
|
@ -0,0 +1,804 @@
|
|||
"""
|
||||
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
|
||||
from matplotlib import cbook
|
||||
import matplotlib.colors as colors
|
||||
import matplotlib.cm as cm
|
||||
from matplotlib import docstring
|
||||
import matplotlib.ticker as ticker
|
||||
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
|
||||
|
||||
|
||||
cbook.warn_deprecated(
|
||||
"3.2", name=__name__, obj_type="module", alternative="matplotlib.colorbar")
|
||||
|
||||
|
||||
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:
|
||||
"""
|
||||
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):
|
||||
"""Return 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):
|
||||
"""Return the path for axes patch."""
|
||||
end1, end2 = self.get_end_vertices()
|
||||
verts = [] + end1 + end2 + end1[:1]
|
||||
return Path(verts)
|
||||
|
||||
def get_path_ends(self):
|
||||
"""Return 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 np.iterable(ticks):
|
||||
self.cbar_axis.set_ticks(ticks)
|
||||
elif ticks is not None:
|
||||
self.cbar_axis.set_major_locator(ticks)
|
||||
else:
|
||||
self._select_locator()
|
||||
|
||||
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 the colorbar 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_facecolor(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_facecolor(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))
|
||||
|
||||
del self.solids
|
||||
del self.dividers
|
||||
|
||||
col = self.ax.pcolormesh(
|
||||
*args,
|
||||
cmap=self.cmap, norm=self.norm, shading='flat', alpha=self.alpha)
|
||||
|
||||
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):
|
||||
"""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)
|
||||
|
||||
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 = (self._boundaries[:-1]
|
||||
+ self._boundaries[1:]) / 2
|
||||
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)
|
||||
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):
|
||||
self._boundaries = (
|
||||
self._uniform_y(self.cmap.N + 1) * self.cmap.N - 0.5)
|
||||
self._values = np.arange(self.cmap.N, dtype=np.int16)
|
||||
return
|
||||
elif isinstance(self.norm, colors.BoundaryNorm):
|
||||
self._boundaries = np.array(self.norm.boundaries)
|
||||
self._values = (self._boundaries[:-1] + self._boundaries[1:]) / 2
|
||||
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.geomspace(vmin, 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 the alpha value for transparency."""
|
||||
self.alpha = alpha
|
||||
|
||||
|
||||
class Colorbar(ColorbarBase):
|
||||
def __init__(self, ax, mappable, **kw):
|
||||
# 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.
|
||||
mappable.autoscale_None()
|
||||
|
||||
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 `.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_normal(self, mappable):
|
||||
"""
|
||||
Update solid patches, lines, etc.
|
||||
|
||||
This is meant to be called when the norm of the image or contour plot
|
||||
to which this colorbar belongs changes.
|
||||
|
||||
If the norm on the mappable is different than before, this resets the
|
||||
locator and formatter for the axis, so if these have been customized,
|
||||
they will need to be customized again. However, if the norm only
|
||||
changes values of *vmin*, *vmax* or *cmap* then the old formatter
|
||||
and locator will be preserved.
|
||||
"""
|
||||
self.mappable = mappable
|
||||
self.set_alpha(mappable.get_alpha())
|
||||
self.cmap = mappable.cmap
|
||||
if mappable.norm != self.norm:
|
||||
self.norm = mappable.norm
|
||||
self._reset_locator_formatter_scale()
|
||||
|
||||
self.draw_all()
|
||||
if isinstance(self.mappable, contour.ContourSet):
|
||||
CS = self.mappable
|
||||
if not CS.filled:
|
||||
self.add_lines(CS)
|
||||
self.stale = True
|
||||
|
||||
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)
|
||||
|
||||
mappable.callbacksSM.connect('changed', on_changed)
|
||||
mappable.colorbar = cb
|
||||
ax.figure.sca(ax)
|
||||
return cb
|
653
venv/Lib/site-packages/mpl_toolkits/axes_grid1/inset_locator.py
Normal file
653
venv/Lib/site-packages/mpl_toolkits/axes_grid1/inset_locator.py
Normal file
|
@ -0,0 +1,653 @@
|
|||
"""
|
||||
A collection of functions and objects for creating or placing inset axes.
|
||||
"""
|
||||
|
||||
from matplotlib import cbook, docstring
|
||||
from matplotlib.offsetbox import AnchoredOffsetbox
|
||||
from matplotlib.patches import Patch, Rectangle
|
||||
from matplotlib.path import Path
|
||||
from matplotlib.transforms import Bbox, BboxTransformTo
|
||||
from matplotlib.transforms import IdentityTransform, TransformedBbox
|
||||
|
||||
from . import axes_size as Size
|
||||
from .parasite_axes import HostAxes
|
||||
|
||||
|
||||
class InsetPosition:
|
||||
@docstring.dedent_interpd
|
||||
def __init__(self, parent, lbwh):
|
||||
"""
|
||||
An object for positioning an inset axes.
|
||||
|
||||
This is created by specifying the normalized coordinates in the axes,
|
||||
instead of the figure.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parent : `matplotlib.axes.Axes`
|
||||
Axes to use for normalizing coordinates.
|
||||
|
||||
lbwh : iterable of four floats
|
||||
The left edge, bottom edge, width, and height of the inset axes, in
|
||||
units of the normalized coordinate of the *parent* axes.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:meth:`matplotlib.axes.Axes.set_axes_locator`
|
||||
|
||||
Examples
|
||||
--------
|
||||
The following bounds the inset axes to a box with 20%% of the parent
|
||||
axes's height and 40%% of the width. The size of the axes specified
|
||||
([0, 0, 1, 1]) ensures that the axes completely fills the bounding box:
|
||||
|
||||
>>> parent_axes = plt.gca()
|
||||
>>> ax_ins = plt.axes([0, 0, 1, 1])
|
||||
>>> ip = InsetPosition(ax, [0.5, 0.1, 0.4, 0.2])
|
||||
>>> ax_ins.set_axes_locator(ip)
|
||||
"""
|
||||
self.parent = parent
|
||||
self.lbwh = lbwh
|
||||
|
||||
def __call__(self, ax, renderer):
|
||||
bbox_parent = self.parent.get_position(original=False)
|
||||
trans = BboxTransformTo(bbox_parent)
|
||||
bbox_inset = Bbox.from_bounds(*self.lbwh)
|
||||
bb = TransformedBbox(bbox_inset, trans)
|
||||
return bb
|
||||
|
||||
|
||||
class AnchoredLocatorBase(AnchoredOffsetbox):
|
||||
def __init__(self, bbox_to_anchor, offsetbox, loc,
|
||||
borderpad=0.5, bbox_transform=None):
|
||||
super().__init__(
|
||||
loc, pad=0., child=None, borderpad=borderpad,
|
||||
bbox_to_anchor=bbox_to_anchor, bbox_transform=bbox_transform
|
||||
)
|
||||
|
||||
def draw(self, renderer):
|
||||
raise RuntimeError("No draw method should be called")
|
||||
|
||||
def __call__(self, ax, renderer):
|
||||
self.axes = ax
|
||||
|
||||
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
|
||||
self._update_offset_func(renderer, fontsize)
|
||||
|
||||
width, height, xdescent, ydescent = self.get_extent(renderer)
|
||||
|
||||
px, py = self.get_offset(width, height, 0, 0, renderer)
|
||||
bbox_canvas = Bbox.from_bounds(px, py, width, height)
|
||||
tr = ax.figure.transFigure.inverted()
|
||||
bb = TransformedBbox(bbox_canvas, tr)
|
||||
|
||||
return bb
|
||||
|
||||
|
||||
class AnchoredSizeLocator(AnchoredLocatorBase):
|
||||
def __init__(self, bbox_to_anchor, x_size, y_size, loc,
|
||||
borderpad=0.5, bbox_transform=None):
|
||||
super().__init__(
|
||||
bbox_to_anchor, None, loc,
|
||||
borderpad=borderpad, bbox_transform=bbox_transform
|
||||
)
|
||||
|
||||
self.x_size = Size.from_any(x_size)
|
||||
self.y_size = Size.from_any(y_size)
|
||||
|
||||
def get_extent(self, renderer):
|
||||
bbox = self.get_bbox_to_anchor()
|
||||
dpi = renderer.points_to_pixels(72.)
|
||||
|
||||
r, a = self.x_size.get_size(renderer)
|
||||
width = bbox.width * r + a * dpi
|
||||
r, a = self.y_size.get_size(renderer)
|
||||
height = bbox.height * r + a * dpi
|
||||
|
||||
xd, yd = 0, 0
|
||||
|
||||
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
|
||||
pad = self.pad * fontsize
|
||||
|
||||
return width + 2 * pad, height + 2 * pad, xd + pad, yd + pad
|
||||
|
||||
|
||||
class AnchoredZoomLocator(AnchoredLocatorBase):
|
||||
def __init__(self, parent_axes, zoom, loc,
|
||||
borderpad=0.5,
|
||||
bbox_to_anchor=None,
|
||||
bbox_transform=None):
|
||||
self.parent_axes = parent_axes
|
||||
self.zoom = zoom
|
||||
if bbox_to_anchor is None:
|
||||
bbox_to_anchor = parent_axes.bbox
|
||||
super().__init__(
|
||||
bbox_to_anchor, None, loc, borderpad=borderpad,
|
||||
bbox_transform=bbox_transform)
|
||||
|
||||
def get_extent(self, renderer):
|
||||
bb = TransformedBbox(self.axes.viewLim, self.parent_axes.transData)
|
||||
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
|
||||
pad = self.pad * fontsize
|
||||
return (abs(bb.width * self.zoom) + 2 * pad,
|
||||
abs(bb.height * self.zoom) + 2 * pad,
|
||||
pad, pad)
|
||||
|
||||
|
||||
class BboxPatch(Patch):
|
||||
@docstring.dedent_interpd
|
||||
def __init__(self, bbox, **kwargs):
|
||||
"""
|
||||
Patch showing the shape bounded by a Bbox.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bbox : `matplotlib.transforms.Bbox`
|
||||
Bbox to use for the extents of this patch.
|
||||
|
||||
**kwargs
|
||||
Patch properties. Valid arguments include:
|
||||
|
||||
%(Patch)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):
|
||||
# docstring inherited
|
||||
x0, y0, x1, y1 = self.bbox.extents
|
||||
return Path([(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)],
|
||||
closed=True)
|
||||
|
||||
|
||||
class BboxConnector(Patch):
|
||||
@staticmethod
|
||||
def get_bbox_edge_pos(bbox, loc):
|
||||
"""
|
||||
Helper function to obtain the location of a corner of a bbox
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bbox : `matplotlib.transforms.Bbox`
|
||||
|
||||
loc : {1, 2, 3, 4}
|
||||
Corner of *bbox*. Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
Returns
|
||||
-------
|
||||
x, y : float
|
||||
Coordinates of the corner specified by *loc*.
|
||||
"""
|
||||
x0, y0, x1, y1 = bbox.extents
|
||||
if loc == 1:
|
||||
return x1, y1
|
||||
elif loc == 2:
|
||||
return x0, y1
|
||||
elif loc == 3:
|
||||
return x0, y0
|
||||
elif loc == 4:
|
||||
return x1, y0
|
||||
|
||||
@staticmethod
|
||||
def connect_bbox(bbox1, bbox2, loc1, loc2=None):
|
||||
"""
|
||||
Helper function to obtain a Path from one bbox to another.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bbox1, bbox2 : `matplotlib.transforms.Bbox`
|
||||
Bounding boxes to connect.
|
||||
|
||||
loc1 : {1, 2, 3, 4}
|
||||
Corner of *bbox1* to use. Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
loc2 : {1, 2, 3, 4}, optional
|
||||
Corner of *bbox2* to use. If None, defaults to *loc1*.
|
||||
Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
Returns
|
||||
-------
|
||||
path : `matplotlib.path.Path`
|
||||
A line segment from the *loc1* corner of *bbox1* to the *loc2*
|
||||
corner of *bbox2*.
|
||||
"""
|
||||
if isinstance(bbox1, Rectangle):
|
||||
bbox1 = TransformedBbox(Bbox.unit(), bbox1.get_transform())
|
||||
if isinstance(bbox2, Rectangle):
|
||||
bbox2 = TransformedBbox(Bbox.unit(), bbox2.get_transform())
|
||||
if loc2 is None:
|
||||
loc2 = loc1
|
||||
x1, y1 = BboxConnector.get_bbox_edge_pos(bbox1, loc1)
|
||||
x2, y2 = BboxConnector.get_bbox_edge_pos(bbox2, loc2)
|
||||
return Path([[x1, y1], [x2, y2]])
|
||||
|
||||
@docstring.dedent_interpd
|
||||
def __init__(self, bbox1, bbox2, loc1, loc2=None, **kwargs):
|
||||
"""
|
||||
Connect two bboxes with a straight line.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bbox1, bbox2 : `matplotlib.transforms.Bbox`
|
||||
Bounding boxes to connect.
|
||||
|
||||
loc1 : {1, 2, 3, 4}
|
||||
Corner of *bbox1* to draw the line. Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
loc2 : {1, 2, 3, 4}, optional
|
||||
Corner of *bbox2* to draw the line. If None, defaults to *loc1*.
|
||||
Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
**kwargs
|
||||
Patch properties for the line drawn. Valid arguments include:
|
||||
|
||||
%(Patch)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 = bool({'fc', 'facecolor', 'color'}.intersection(kwargs))
|
||||
Patch.__init__(self, fill=fill, **kwargs)
|
||||
self.bbox1 = bbox1
|
||||
self.bbox2 = bbox2
|
||||
self.loc1 = loc1
|
||||
self.loc2 = loc2
|
||||
|
||||
def get_path(self):
|
||||
# docstring inherited
|
||||
return self.connect_bbox(self.bbox1, self.bbox2,
|
||||
self.loc1, self.loc2)
|
||||
|
||||
|
||||
class BboxConnectorPatch(BboxConnector):
|
||||
@docstring.dedent_interpd
|
||||
def __init__(self, bbox1, bbox2, loc1a, loc2a, loc1b, loc2b, **kwargs):
|
||||
"""
|
||||
Connect two bboxes with a quadrilateral.
|
||||
|
||||
The quadrilateral is specified by two lines that start and end at
|
||||
corners of the bboxes. The four sides of the quadrilateral are defined
|
||||
by the two lines given, the line between the two corners specified in
|
||||
*bbox1* and the line between the two corners specified in *bbox2*.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bbox1, bbox2 : `matplotlib.transforms.Bbox`
|
||||
Bounding boxes to connect.
|
||||
|
||||
loc1a, loc2a : {1, 2, 3, 4}
|
||||
Corners of *bbox1* and *bbox2* to draw the first line.
|
||||
Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
loc1b, loc2b : {1, 2, 3, 4}
|
||||
Corners of *bbox1* and *bbox2* to draw the second line.
|
||||
Valid values are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4
|
||||
|
||||
**kwargs
|
||||
Patch properties for the line drawn:
|
||||
|
||||
%(Patch)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):
|
||||
# docstring inherited
|
||||
path1 = self.connect_bbox(self.bbox1, self.bbox2, self.loc1, self.loc2)
|
||||
path2 = self.connect_bbox(self.bbox2, self.bbox1,
|
||||
self.loc2b, self.loc1b)
|
||||
path_merged = [*path1.vertices, *path2.vertices, path1.vertices[0]]
|
||||
return Path(path_merged)
|
||||
|
||||
|
||||
def _add_inset_axes(parent_axes, inset_axes):
|
||||
"""Helper function to add an inset axes and disable navigation in it"""
|
||||
parent_axes.figure.add_axes(inset_axes)
|
||||
inset_axes.set_navigate(False)
|
||||
|
||||
|
||||
@docstring.dedent_interpd
|
||||
def inset_axes(parent_axes, width, height, loc='upper right',
|
||||
bbox_to_anchor=None, bbox_transform=None,
|
||||
axes_class=None,
|
||||
axes_kwargs=None,
|
||||
borderpad=0.5):
|
||||
"""
|
||||
Create an inset axes with a given width and height.
|
||||
|
||||
Both sizes used can be specified either in inches or percentage.
|
||||
For example,::
|
||||
|
||||
inset_axes(parent_axes, width='40%%', height='30%%', loc=3)
|
||||
|
||||
creates in inset axes in the lower left corner of *parent_axes* which spans
|
||||
over 30%% in height and 40%% in width of the *parent_axes*. Since the usage
|
||||
of `.inset_axes` may become slightly tricky when exceeding such standard
|
||||
cases, it is recommended to read :doc:`the examples
|
||||
</gallery/axes_grid1/inset_locator_demo>`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The meaning of *bbox_to_anchor* and *bbox_to_transform* is interpreted
|
||||
differently from that of legend. The value of bbox_to_anchor
|
||||
(or the return value of its get_points method; the default is
|
||||
*parent_axes.bbox*) is transformed by the bbox_transform (the default
|
||||
is Identity transform) and then interpreted as points in the pixel
|
||||
coordinate (which is dpi dependent).
|
||||
|
||||
Thus, following three calls are identical and creates an inset axes
|
||||
with respect to the *parent_axes*::
|
||||
|
||||
axins = inset_axes(parent_axes, "30%%", "40%%")
|
||||
axins = inset_axes(parent_axes, "30%%", "40%%",
|
||||
bbox_to_anchor=parent_axes.bbox)
|
||||
axins = inset_axes(parent_axes, "30%%", "40%%",
|
||||
bbox_to_anchor=(0, 0, 1, 1),
|
||||
bbox_transform=parent_axes.transAxes)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parent_axes : `matplotlib.axes.Axes`
|
||||
Axes to place the inset axes.
|
||||
|
||||
width, height : float or str
|
||||
Size of the inset axes to create. If a float is provided, it is
|
||||
the size in inches, e.g. *width=1.3*. If a string is provided, it is
|
||||
the size in relative units, e.g. *width='40%%'*. By default, i.e. if
|
||||
neither *bbox_to_anchor* nor *bbox_transform* are specified, those
|
||||
are relative to the parent_axes. Otherwise they are to be understood
|
||||
relative to the bounding box provided via *bbox_to_anchor*.
|
||||
|
||||
loc : int or str, default: 1
|
||||
Location to place the inset axes. The valid locations are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
bbox_to_anchor : tuple or `matplotlib.transforms.BboxBase`, optional
|
||||
Bbox that the inset axes will be anchored to. If None,
|
||||
a tuple of (0, 0, 1, 1) is used if *bbox_transform* is set
|
||||
to *parent_axes.transAxes* or *parent_axes.figure.transFigure*.
|
||||
Otherwise, *parent_axes.bbox* is used. If a tuple, can be either
|
||||
[left, bottom, width, height], or [left, bottom].
|
||||
If the kwargs *width* and/or *height* are specified in relative units,
|
||||
the 2-tuple [left, bottom] cannot be used. Note that,
|
||||
unless *bbox_transform* is set, the units of the bounding box
|
||||
are interpreted in the pixel coordinate. When using *bbox_to_anchor*
|
||||
with tuple, it almost always makes sense to also specify
|
||||
a *bbox_transform*. This might often be the axes transform
|
||||
*parent_axes.transAxes*.
|
||||
|
||||
bbox_transform : `matplotlib.transforms.Transform`, optional
|
||||
Transformation for the bbox that contains the inset axes.
|
||||
If None, a `.transforms.IdentityTransform` is used. The value
|
||||
of *bbox_to_anchor* (or the return value of its get_points method)
|
||||
is transformed by the *bbox_transform* and then interpreted
|
||||
as points in the pixel coordinate (which is dpi dependent).
|
||||
You may provide *bbox_to_anchor* in some normalized coordinate,
|
||||
and give an appropriate transform (e.g., *parent_axes.transAxes*).
|
||||
|
||||
axes_class : `matplotlib.axes.Axes` type, optional
|
||||
If specified, the inset axes created will be created with this class's
|
||||
constructor.
|
||||
|
||||
axes_kwargs : dict, optional
|
||||
Keyworded arguments to pass to the constructor of the inset axes.
|
||||
Valid arguments include:
|
||||
|
||||
%(Axes)s
|
||||
|
||||
borderpad : float, default: 0.5
|
||||
Padding between inset axes and the bbox_to_anchor.
|
||||
The units are axes font size, i.e. for a default font size of 10 points
|
||||
*borderpad = 0.5* is equivalent to a padding of 5 points.
|
||||
|
||||
Returns
|
||||
-------
|
||||
inset_axes : *axes_class*
|
||||
Inset axes object created.
|
||||
"""
|
||||
|
||||
if axes_class is None:
|
||||
axes_class = HostAxes
|
||||
|
||||
if axes_kwargs is None:
|
||||
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position())
|
||||
else:
|
||||
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(),
|
||||
**axes_kwargs)
|
||||
|
||||
if bbox_transform in [parent_axes.transAxes,
|
||||
parent_axes.figure.transFigure]:
|
||||
if bbox_to_anchor is None:
|
||||
cbook._warn_external("Using the axes or figure transform "
|
||||
"requires a bounding box in the respective "
|
||||
"coordinates. "
|
||||
"Using bbox_to_anchor=(0, 0, 1, 1) now.")
|
||||
bbox_to_anchor = (0, 0, 1, 1)
|
||||
|
||||
if bbox_to_anchor is None:
|
||||
bbox_to_anchor = parent_axes.bbox
|
||||
|
||||
if (isinstance(bbox_to_anchor, tuple) and
|
||||
(isinstance(width, str) or isinstance(height, str))):
|
||||
if len(bbox_to_anchor) != 4:
|
||||
raise ValueError("Using relative units for width or height "
|
||||
"requires to provide a 4-tuple or a "
|
||||
"`Bbox` instance to `bbox_to_anchor.")
|
||||
|
||||
axes_locator = AnchoredSizeLocator(bbox_to_anchor,
|
||||
width, height,
|
||||
loc=loc,
|
||||
bbox_transform=bbox_transform,
|
||||
borderpad=borderpad)
|
||||
|
||||
inset_axes.set_axes_locator(axes_locator)
|
||||
|
||||
_add_inset_axes(parent_axes, inset_axes)
|
||||
|
||||
return inset_axes
|
||||
|
||||
|
||||
@docstring.dedent_interpd
|
||||
def zoomed_inset_axes(parent_axes, zoom, loc='upper right',
|
||||
bbox_to_anchor=None, bbox_transform=None,
|
||||
axes_class=None,
|
||||
axes_kwargs=None,
|
||||
borderpad=0.5):
|
||||
"""
|
||||
Create an anchored inset axes by scaling a parent axes. For usage, also see
|
||||
:doc:`the examples </gallery/axes_grid1/inset_locator_demo2>`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parent_axes : `matplotlib.axes.Axes`
|
||||
Axes to place the inset axes.
|
||||
|
||||
zoom : float
|
||||
Scaling factor of the data axes. *zoom* > 1 will enlargen the
|
||||
coordinates (i.e., "zoomed in"), while *zoom* < 1 will shrink the
|
||||
coordinates (i.e., "zoomed out").
|
||||
|
||||
loc : int or str, default: 'upper right'
|
||||
Location to place the inset axes. The valid locations are::
|
||||
|
||||
'upper right' : 1,
|
||||
'upper left' : 2,
|
||||
'lower left' : 3,
|
||||
'lower right' : 4,
|
||||
'right' : 5,
|
||||
'center left' : 6,
|
||||
'center right' : 7,
|
||||
'lower center' : 8,
|
||||
'upper center' : 9,
|
||||
'center' : 10
|
||||
|
||||
bbox_to_anchor : tuple or `matplotlib.transforms.BboxBase`, optional
|
||||
Bbox that the inset axes will be anchored to. If None,
|
||||
*parent_axes.bbox* is used. If a tuple, can be either
|
||||
[left, bottom, width, height], or [left, bottom].
|
||||
If the kwargs *width* and/or *height* are specified in relative units,
|
||||
the 2-tuple [left, bottom] cannot be used. Note that
|
||||
the units of the bounding box are determined through the transform
|
||||
in use. When using *bbox_to_anchor* it almost always makes sense to
|
||||
also specify a *bbox_transform*. This might often be the axes transform
|
||||
*parent_axes.transAxes*.
|
||||
|
||||
bbox_transform : `matplotlib.transforms.Transform`, optional
|
||||
Transformation for the bbox that contains the inset axes.
|
||||
If None, a `.transforms.IdentityTransform` is used (i.e. pixel
|
||||
coordinates). This is useful when not providing any argument to
|
||||
*bbox_to_anchor*. When using *bbox_to_anchor* it almost always makes
|
||||
sense to also specify a *bbox_transform*. This might often be the
|
||||
axes transform *parent_axes.transAxes*. Inversely, when specifying
|
||||
the axes- or figure-transform here, be aware that not specifying
|
||||
*bbox_to_anchor* will use *parent_axes.bbox*, the units of which are
|
||||
in display (pixel) coordinates.
|
||||
|
||||
axes_class : `matplotlib.axes.Axes` type, optional
|
||||
If specified, the inset axes created will be created with this class's
|
||||
constructor.
|
||||
|
||||
axes_kwargs : dict, optional
|
||||
Keyworded arguments to pass to the constructor of the inset axes.
|
||||
Valid arguments include:
|
||||
|
||||
%(Axes)s
|
||||
|
||||
borderpad : float, default: 0.5
|
||||
Padding between inset axes and the bbox_to_anchor.
|
||||
The units are axes font size, i.e. for a default font size of 10 points
|
||||
*borderpad = 0.5* is equivalent to a padding of 5 points.
|
||||
|
||||
Returns
|
||||
-------
|
||||
inset_axes : *axes_class*
|
||||
Inset axes object created.
|
||||
"""
|
||||
|
||||
if axes_class is None:
|
||||
axes_class = HostAxes
|
||||
|
||||
if axes_kwargs is None:
|
||||
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position())
|
||||
else:
|
||||
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(),
|
||||
**axes_kwargs)
|
||||
|
||||
axes_locator = AnchoredZoomLocator(parent_axes, zoom=zoom, loc=loc,
|
||||
bbox_to_anchor=bbox_to_anchor,
|
||||
bbox_transform=bbox_transform,
|
||||
borderpad=borderpad)
|
||||
inset_axes.set_axes_locator(axes_locator)
|
||||
|
||||
_add_inset_axes(parent_axes, inset_axes)
|
||||
|
||||
return inset_axes
|
||||
|
||||
|
||||
@docstring.dedent_interpd
|
||||
def mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs):
|
||||
"""
|
||||
Draw a box to mark the location of an area represented by an inset axes.
|
||||
|
||||
This function draws a box in *parent_axes* at the bounding box of
|
||||
*inset_axes*, and shows a connection with the inset axes by drawing lines
|
||||
at the corners, giving a "zoomed in" effect.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parent_axes : `matplotlib.axes.Axes`
|
||||
Axes which contains the area of the inset axes.
|
||||
|
||||
inset_axes : `matplotlib.axes.Axes`
|
||||
The inset axes.
|
||||
|
||||
loc1, loc2 : {1, 2, 3, 4}
|
||||
Corners to use for connecting the inset axes and the area in the
|
||||
parent axes.
|
||||
|
||||
**kwargs
|
||||
Patch properties for the lines and box drawn:
|
||||
|
||||
%(Patch)s
|
||||
|
||||
Returns
|
||||
-------
|
||||
pp : `matplotlib.patches.Patch`
|
||||
The patch drawn to represent the area of the inset axes.
|
||||
|
||||
p1, p2 : `matplotlib.patches.Patch`
|
||||
The patches connecting two corners of the inset axes and its area.
|
||||
"""
|
||||
rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData)
|
||||
|
||||
if 'fill' in kwargs:
|
||||
pp = BboxPatch(rect, **kwargs)
|
||||
else:
|
||||
fill = bool({'fc', 'facecolor', 'color'}.intersection(kwargs))
|
||||
pp = BboxPatch(rect, fill=fill, **kwargs)
|
||||
parent_axes.add_patch(pp)
|
||||
|
||||
p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1, **kwargs)
|
||||
inset_axes.add_patch(p1)
|
||||
p1.set_clip_on(False)
|
||||
p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2, **kwargs)
|
||||
inset_axes.add_patch(p2)
|
||||
p2.set_clip_on(False)
|
||||
|
||||
return pp, p1, p2
|
134
venv/Lib/site-packages/mpl_toolkits/axes_grid1/mpl_axes.py
Normal file
134
venv/Lib/site-packages/mpl_toolkits/axes_grid1/mpl_axes.py
Normal file
|
@ -0,0 +1,134 @@
|
|||
import matplotlib.axes as maxes
|
||||
from matplotlib.artist import Artist
|
||||
from matplotlib.axis import XAxis, YAxis
|
||||
|
||||
|
||||
class SimpleChainedObjects:
|
||||
def __init__(self, objects):
|
||||
self._objects = objects
|
||||
|
||||
def __getattr__(self, k):
|
||||
_a = SimpleChainedObjects([getattr(a, k) for a in self._objects])
|
||||
return _a
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
for m in self._objects:
|
||||
m(*args, **kwargs)
|
||||
|
||||
|
||||
class Axes(maxes.Axes):
|
||||
|
||||
class AxisDict(dict):
|
||||
def __init__(self, axes):
|
||||
self.axes = axes
|
||||
super().__init__()
|
||||
|
||||
def __getitem__(self, k):
|
||||
if isinstance(k, tuple):
|
||||
r = SimpleChainedObjects(
|
||||
# super() within a list comprehension needs explicit args.
|
||||
[super(Axes.AxisDict, self).__getitem__(k1) for k1 in k])
|
||||
return r
|
||||
elif isinstance(k, slice):
|
||||
if k.start is None and k.stop is None and k.step is None:
|
||||
return SimpleChainedObjects(list(self.values()))
|
||||
else:
|
||||
raise ValueError("Unsupported slice")
|
||||
else:
|
||||
return dict.__getitem__(self, k)
|
||||
|
||||
def __call__(self, *v, **kwargs):
|
||||
return maxes.Axes.axis(self.axes, *v, **kwargs)
|
||||
|
||||
def _init_axis_artists(self, axes=None):
|
||||
if axes is None:
|
||||
axes = self
|
||||
self._axislines = self.AxisDict(self)
|
||||
self._axislines.update(
|
||||
bottom=SimpleAxisArtist(self.xaxis, 1, self.spines["bottom"]),
|
||||
top=SimpleAxisArtist(self.xaxis, 2, self.spines["top"]),
|
||||
left=SimpleAxisArtist(self.yaxis, 1, self.spines["left"]),
|
||||
right=SimpleAxisArtist(self.yaxis, 2, self.spines["right"]))
|
||||
|
||||
@property
|
||||
def axis(self):
|
||||
return self._axislines
|
||||
|
||||
def cla(self):
|
||||
super().cla()
|
||||
self._init_axis_artists()
|
||||
|
||||
|
||||
class SimpleAxisArtist(Artist):
|
||||
def __init__(self, axis, axisnum, spine):
|
||||
self._axis = axis
|
||||
self._axisnum = axisnum
|
||||
self.line = spine
|
||||
|
||||
if isinstance(axis, XAxis):
|
||||
self._axis_direction = ["bottom", "top"][axisnum-1]
|
||||
elif isinstance(axis, YAxis):
|
||||
self._axis_direction = ["left", "right"][axisnum-1]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"axis must be instance of XAxis or YAxis, but got {axis}")
|
||||
Artist.__init__(self)
|
||||
|
||||
@property
|
||||
def major_ticks(self):
|
||||
tickline = "tick%dline" % self._axisnum
|
||||
return SimpleChainedObjects([getattr(tick, tickline)
|
||||
for tick in self._axis.get_major_ticks()])
|
||||
|
||||
@property
|
||||
def major_ticklabels(self):
|
||||
label = "label%d" % self._axisnum
|
||||
return SimpleChainedObjects([getattr(tick, label)
|
||||
for tick in self._axis.get_major_ticks()])
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
return self._axis.label
|
||||
|
||||
def set_visible(self, b):
|
||||
self.toggle(all=b)
|
||||
self.line.set_visible(b)
|
||||
self._axis.set_visible(True)
|
||||
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)
|
433
venv/Lib/site-packages/mpl_toolkits/axes_grid1/parasite_axes.py
Normal file
433
venv/Lib/site-packages/mpl_toolkits/axes_grid1/parasite_axes.py
Normal file
|
@ -0,0 +1,433 @@
|
|||
import functools
|
||||
|
||||
from matplotlib import artist as martist, cbook, transforms as mtransforms
|
||||
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)
|
||||
|
||||
def pick(self, mouseevent):
|
||||
# This most likely goes to Artist.pick (depending on axes_class given
|
||||
# to the factory), which only handles pick events registered on the
|
||||
# axes associated with each child:
|
||||
super().pick(mouseevent)
|
||||
# But parasite axes are additionally given pick events from their host
|
||||
# axes (cf. HostAxesBase.pick), which we handle here:
|
||||
for a in self.get_children():
|
||||
if (hasattr(mouseevent.inaxes, "parasites")
|
||||
and self in mouseevent.inaxes.parasites):
|
||||
a.pick(mouseevent)
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def parasite_axes_class_factory(axes_class=None):
|
||||
if axes_class is None:
|
||||
cbook.warn_deprecated(
|
||||
"3.3", message="Support for passing None to "
|
||||
"parasite_axes_class_factory is deprecated since %(since)s and "
|
||||
"will be removed %(removal)s; explicitly pass the default Axes "
|
||||
"class instead.")
|
||||
axes_class = Axes
|
||||
|
||||
return type("%sParasite" % axes_class.__name__,
|
||||
(ParasiteAxesBase, axes_class), {})
|
||||
|
||||
|
||||
ParasiteAxes = parasite_axes_class_factory(Axes)
|
||||
|
||||
|
||||
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):
|
||||
cbook._check_in_list([None, "equal", "transform"], mode=mode)
|
||||
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:
|
||||
cbook._check_in_list([None, "equal", "transform"], mode=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:
|
||||
cbook.warn_deprecated(
|
||||
"3.3", message="Support for passing None to "
|
||||
"parasite_axes_auxtrans_class_factory is deprecated since "
|
||||
"%(since)s and will be removed %(removal)s; explicitly pass the "
|
||||
"default ParasiteAxes class instead.")
|
||||
parasite_axes_class = ParasiteAxes
|
||||
elif not issubclass(axes_class, ParasiteAxesBase):
|
||||
parasite_axes_class = parasite_axes_class_factory(axes_class)
|
||||
else:
|
||||
parasite_axes_class = axes_class
|
||||
return type("%sParasiteAuxTrans" % parasite_axes_class.__name__,
|
||||
(ParasiteAxesAuxTransBase, parasite_axes_class),
|
||||
{'name': 'parasite_axes'})
|
||||
|
||||
|
||||
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes)
|
||||
|
||||
|
||||
class HostAxesBase:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.parasites = []
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_aux_axes(self, tr, viewlim_mode="equal", axes_class=ParasiteAxes):
|
||||
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
|
||||
# Anything you draw in ax2 will match the ticks and grids of ax1.
|
||||
self.parasites.append(ax2)
|
||||
ax2._remove_method = self.parasites.remove
|
||||
return ax2
|
||||
|
||||
def _get_legend_handles(self, legend_handler_map=None):
|
||||
all_handles = super()._get_legend_handles()
|
||||
for ax in self.parasites:
|
||||
all_handles.extend(ax._get_legend_handles(legend_handler_map))
|
||||
return all_handles
|
||||
|
||||
def draw(self, renderer):
|
||||
|
||||
orig_artists = list(self.artists)
|
||||
orig_images = list(self.images)
|
||||
|
||||
if hasattr(self, "get_axes_locator"):
|
||||
locator = self.get_axes_locator()
|
||||
if locator:
|
||||
pos = locator(self, renderer)
|
||||
self.set_position(pos, which="active")
|
||||
self.apply_aspect(pos)
|
||||
else:
|
||||
self.apply_aspect()
|
||||
else:
|
||||
self.apply_aspect()
|
||||
|
||||
rect = self.get_position()
|
||||
|
||||
for ax in self.parasites:
|
||||
ax.apply_aspect(rect)
|
||||
images, artists = ax.get_images_artists()
|
||||
self.images.extend(images)
|
||||
self.artists.extend(artists)
|
||||
|
||||
super().draw(renderer)
|
||||
self.artists = orig_artists
|
||||
self.images = orig_images
|
||||
|
||||
def cla(self):
|
||||
for ax in self.parasites:
|
||||
ax.cla()
|
||||
super().cla()
|
||||
|
||||
def pick(self, mouseevent):
|
||||
super().pick(mouseevent)
|
||||
# Also pass pick events on to parasite axes and, in turn, their
|
||||
# children (cf. ParasiteAxesBase.pick)
|
||||
for a in self.parasites:
|
||||
a.pick(mouseevent)
|
||||
|
||||
def twinx(self, axes_class=None):
|
||||
"""
|
||||
Create a twin of Axes with a shared x-axis but independent y-axis.
|
||||
|
||||
The y-axis of self will have ticks on the left and the returned axes
|
||||
will have ticks on the right.
|
||||
"""
|
||||
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 with a shared y-axis but independent x-axis.
|
||||
|
||||
The x-axis of self will have ticks on the bottom and the returned axes
|
||||
will have ticks on the top.
|
||||
"""
|
||||
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 with no shared axis.
|
||||
|
||||
While self will have ticks on the left and bottom axis, the returned
|
||||
axes will have ticks on the top and right axis.
|
||||
"""
|
||||
if 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],
|
||||
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:
|
||||
cbook.warn_deprecated(
|
||||
"3.3", message="Support for passing None to host_axes_class is "
|
||||
"deprecated since %(since)s and will be removed %(removed)s; "
|
||||
"explicitly pass the default Axes class instead.")
|
||||
axes_class = Axes
|
||||
|
||||
def _get_base_axes(self):
|
||||
return axes_class
|
||||
|
||||
return type("%sHostAxes" % axes_class.__name__,
|
||||
(HostAxesBase, axes_class),
|
||||
{'_get_base_axes': _get_base_axes})
|
||||
|
||||
|
||||
def host_subplot_class_factory(axes_class):
|
||||
host_axes_class = host_axes_class_factory(axes_class)
|
||||
subplot_host_class = subplot_class_factory(host_axes_class)
|
||||
return subplot_host_class
|
||||
|
||||
|
||||
HostAxes = host_axes_class_factory(Axes)
|
||||
SubplotHost = subplot_class_factory(HostAxes)
|
||||
|
||||
|
||||
def host_axes(*args, axes_class=Axes, figure=None, **kwargs):
|
||||
"""
|
||||
Create axes that can act as a hosts to parasitic axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
figure : `matplotlib.figure.Figure`
|
||||
Figure to which the axes will be added. Defaults to the current figure
|
||||
`.pyplot.gcf()`.
|
||||
|
||||
*args, **kwargs
|
||||
Will be passed on to the underlying ``Axes`` object creation.
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
host_axes_class = host_axes_class_factory(axes_class)
|
||||
if figure is None:
|
||||
figure = plt.gcf()
|
||||
ax = host_axes_class(figure, *args, **kwargs)
|
||||
figure.add_axes(ax)
|
||||
plt.draw_if_interactive()
|
||||
return ax
|
||||
|
||||
|
||||
def host_subplot(*args, axes_class=Axes, figure=None, **kwargs):
|
||||
"""
|
||||
Create a subplot that can act as a host to parasitic axes.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
figure : `matplotlib.figure.Figure`
|
||||
Figure to which the subplot will be added. Defaults to the current
|
||||
figure `.pyplot.gcf()`.
|
||||
|
||||
*args, **kwargs
|
||||
Will be passed on to the underlying ``Axes`` object creation.
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
host_subplot_class = host_subplot_class_factory(axes_class)
|
||||
if figure is None:
|
||||
figure = plt.gcf()
|
||||
ax = host_subplot_class(figure, *args, **kwargs)
|
||||
figure.add_subplot(ax)
|
||||
plt.draw_if_interactive()
|
||||
return ax
|
15
venv/Lib/site-packages/mpl_toolkits/axisartist/__init__.py
Normal file
15
venv/Lib/site-packages/mpl_toolkits/axisartist/__init__.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from .axislines import (
|
||||
Axes, AxesZero, AxisArtistHelper, AxisArtistHelperRectlinear,
|
||||
GridHelperBase, GridHelperRectlinear, Subplot, SubplotZero)
|
||||
from .axis_artist import AxisArtist, GridlinesCollection
|
||||
from .grid_helper_curvelinear import GridHelperCurveLinear
|
||||
from .floating_axes import FloatingAxes, FloatingSubplot
|
||||
from mpl_toolkits.axes_grid1.parasite_axes import (
|
||||
host_axes_class_factory, parasite_axes_class_factory,
|
||||
parasite_axes_auxtrans_class_factory, subplot_class_factory)
|
||||
|
||||
|
||||
ParasiteAxes = parasite_axes_class_factory(Axes)
|
||||
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes)
|
||||
HostAxes = host_axes_class_factory(Axes)
|
||||
SubplotHost = subplot_class_factory(HostAxes)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
405
venv/Lib/site-packages/mpl_toolkits/axisartist/angle_helper.py
Normal file
405
venv/Lib/site-packages/mpl_toolkits/axisartist/angle_helper.py
Normal file
|
@ -0,0 +1,405 @@
|
|||
import numpy as np
|
||||
import math
|
||||
|
||||
from matplotlib import cbook
|
||||
from mpl_toolkits.axisartist.grid_finder import ExtremeFinderSimple
|
||||
|
||||
|
||||
def select_step_degree(dv):
|
||||
|
||||
degree_limits_ = [1.5, 3, 7, 13, 20, 40, 70, 120, 270, 520]
|
||||
degree_steps_ = [1, 2, 5, 10, 15, 30, 45, 90, 180, 360]
|
||||
degree_factors = [1.] * len(degree_steps_)
|
||||
|
||||
minsec_limits_ = [1.5, 2.5, 3.5, 8, 11, 18, 25, 45]
|
||||
minsec_steps_ = [1, 2, 3, 5, 10, 15, 20, 30]
|
||||
|
||||
minute_limits_ = np.array(minsec_limits_) / 60
|
||||
minute_factors = [60.] * len(minute_limits_)
|
||||
|
||||
second_limits_ = np.array(minsec_limits_) / 3600
|
||||
second_factors = [3600.] * len(second_limits_)
|
||||
|
||||
degree_limits = [*second_limits_, *minute_limits_, *degree_limits_]
|
||||
degree_steps = [*minsec_steps_, *minsec_steps_, *degree_steps_]
|
||||
degree_factors = [*second_factors, *minute_factors, *degree_factors]
|
||||
|
||||
n = np.searchsorted(degree_limits, dv)
|
||||
step = degree_steps[n]
|
||||
factor = degree_factors[n]
|
||||
|
||||
return step, factor
|
||||
|
||||
|
||||
def select_step_hour(dv):
|
||||
|
||||
hour_limits_ = [1.5, 2.5, 3.5, 5, 7, 10, 15, 21, 36]
|
||||
hour_steps_ = [1, 2, 3, 4, 6, 8, 12, 18, 24]
|
||||
hour_factors = [1.] * len(hour_steps_)
|
||||
|
||||
minsec_limits_ = [1.5, 2.5, 3.5, 4.5, 5.5, 8, 11, 14, 18, 25, 45]
|
||||
minsec_steps_ = [1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30]
|
||||
|
||||
minute_limits_ = np.array(minsec_limits_) / 60
|
||||
minute_factors = [60.] * len(minute_limits_)
|
||||
|
||||
second_limits_ = np.array(minsec_limits_) / 3600
|
||||
second_factors = [3600.] * len(second_limits_)
|
||||
|
||||
hour_limits = [*second_limits_, *minute_limits_, *hour_limits_]
|
||||
hour_steps = [*minsec_steps_, *minsec_steps_, *hour_steps_]
|
||||
hour_factors = [*second_factors, *minute_factors, *hour_factors]
|
||||
|
||||
n = np.searchsorted(hour_limits, dv)
|
||||
step = hour_steps[n]
|
||||
factor = hour_factors[n]
|
||||
|
||||
return step, factor
|
||||
|
||||
|
||||
def select_step_sub(dv):
|
||||
|
||||
# subarcsec or degree
|
||||
tmp = 10.**(int(math.log10(dv))-1.)
|
||||
|
||||
factor = 1./tmp
|
||||
|
||||
if 1.5*tmp >= dv:
|
||||
step = 1
|
||||
elif 3.*tmp >= dv:
|
||||
step = 2
|
||||
elif 7.*tmp >= dv:
|
||||
step = 5
|
||||
else:
|
||||
step = 1
|
||||
factor = 0.1*factor
|
||||
|
||||
return step, factor
|
||||
|
||||
|
||||
def select_step(v1, v2, nv, hour=False, include_last=True,
|
||||
threshold_factor=3600.):
|
||||
|
||||
if v1 > v2:
|
||||
v1, v2 = v2, v1
|
||||
|
||||
dv = (v2 - v1) / nv
|
||||
|
||||
if hour:
|
||||
_select_step = select_step_hour
|
||||
cycle = 24.
|
||||
else:
|
||||
_select_step = select_step_degree
|
||||
cycle = 360.
|
||||
|
||||
# for degree
|
||||
if dv > 1 / threshold_factor:
|
||||
step, factor = _select_step(dv)
|
||||
else:
|
||||
step, factor = select_step_sub(dv*threshold_factor)
|
||||
|
||||
factor = factor * threshold_factor
|
||||
|
||||
levs = np.arange(np.floor(v1 * factor / step),
|
||||
np.ceil(v2 * factor / step) + 0.5,
|
||||
dtype=int) * step
|
||||
|
||||
# n : number of valid levels. If there is a cycle, e.g., [0, 90, 180,
|
||||
# 270, 360], the grid line needs to be extended from 0 to 360, so
|
||||
# we need to return the whole array. However, the last level (360)
|
||||
# needs to be ignored often. In this case, so we return n=4.
|
||||
|
||||
n = len(levs)
|
||||
|
||||
# we need to check the range of values
|
||||
# for example, -90 to 90, 0 to 360,
|
||||
|
||||
if factor == 1. and levs[-1] >= levs[0] + cycle: # check for cycle
|
||||
nv = int(cycle / step)
|
||||
if include_last:
|
||||
levs = levs[0] + np.arange(0, nv+1, 1) * step
|
||||
else:
|
||||
levs = levs[0] + np.arange(0, nv, 1) * step
|
||||
|
||||
n = len(levs)
|
||||
|
||||
return np.array(levs), n, factor
|
||||
|
||||
|
||||
def select_step24(v1, v2, nv, include_last=True, threshold_factor=3600):
|
||||
v1, v2 = v1 / 15, v2 / 15
|
||||
levs, n, factor = select_step(v1, v2, nv, hour=True,
|
||||
include_last=include_last,
|
||||
threshold_factor=threshold_factor)
|
||||
return levs * 15, n, factor
|
||||
|
||||
|
||||
def select_step360(v1, v2, nv, include_last=True, threshold_factor=3600):
|
||||
return select_step(v1, v2, nv, hour=False,
|
||||
include_last=include_last,
|
||||
threshold_factor=threshold_factor)
|
||||
|
||||
|
||||
class LocatorBase:
|
||||
@cbook._rename_parameter("3.3", "den", "nbins")
|
||||
def __init__(self, nbins, include_last=True):
|
||||
self.nbins = nbins
|
||||
self._include_last = include_last
|
||||
|
||||
@cbook.deprecated("3.3", alternative="nbins")
|
||||
@property
|
||||
def den(self):
|
||||
return self.nbins
|
||||
|
||||
@den.setter
|
||||
def den(self, v):
|
||||
self.nbins = v
|
||||
|
||||
def set_params(self, nbins=None):
|
||||
if nbins is not None:
|
||||
self.nbins = int(nbins)
|
||||
|
||||
|
||||
class LocatorHMS(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step24(v1, v2, self.nbins, self._include_last)
|
||||
|
||||
|
||||
class LocatorHM(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step24(v1, v2, self.nbins, self._include_last,
|
||||
threshold_factor=60)
|
||||
|
||||
|
||||
class LocatorH(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step24(v1, v2, self.nbins, self._include_last,
|
||||
threshold_factor=1)
|
||||
|
||||
|
||||
class LocatorDMS(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step360(v1, v2, self.nbins, self._include_last)
|
||||
|
||||
|
||||
class LocatorDM(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step360(v1, v2, self.nbins, self._include_last,
|
||||
threshold_factor=60)
|
||||
|
||||
|
||||
class LocatorD(LocatorBase):
|
||||
def __call__(self, v1, v2):
|
||||
return select_step360(v1, v2, self.nbins, self._include_last,
|
||||
threshold_factor=1)
|
||||
|
||||
|
||||
class FormatterDMS:
|
||||
deg_mark = r"^{\circ}"
|
||||
min_mark = r"^{\prime}"
|
||||
sec_mark = r"^{\prime\prime}"
|
||||
|
||||
fmt_d = "$%d" + deg_mark + "$"
|
||||
fmt_ds = r"$%d.%s" + deg_mark + "$"
|
||||
|
||||
# %s for sign
|
||||
fmt_d_m = r"$%s%d" + deg_mark + r"\,%02d" + min_mark + "$"
|
||||
fmt_d_ms = r"$%s%d" + deg_mark + r"\,%02d.%s" + min_mark + "$"
|
||||
|
||||
fmt_d_m_partial = "$%s%d" + deg_mark + r"\,%02d" + min_mark + r"\,"
|
||||
fmt_s_partial = "%02d" + sec_mark + "$"
|
||||
fmt_ss_partial = "%02d.%s" + sec_mark + "$"
|
||||
|
||||
def _get_number_fraction(self, factor):
|
||||
## check for fractional numbers
|
||||
number_fraction = None
|
||||
# check for 60
|
||||
|
||||
for threshold in [1, 60, 3600]:
|
||||
if factor <= threshold:
|
||||
break
|
||||
|
||||
d = factor // threshold
|
||||
int_log_d = int(np.floor(np.log10(d)))
|
||||
if 10**int_log_d == d and d != 1:
|
||||
number_fraction = int_log_d
|
||||
factor = factor // 10**int_log_d
|
||||
return factor, number_fraction
|
||||
|
||||
return factor, number_fraction
|
||||
|
||||
def __call__(self, direction, factor, values):
|
||||
if len(values) == 0:
|
||||
return []
|
||||
|
||||
ss = np.sign(values)
|
||||
signs = ["-" if v < 0 else "" for v in values]
|
||||
|
||||
factor, number_fraction = self._get_number_fraction(factor)
|
||||
|
||||
values = np.abs(values)
|
||||
|
||||
if number_fraction is not None:
|
||||
values, frac_part = divmod(values, 10 ** number_fraction)
|
||||
frac_fmt = "%%0%dd" % (number_fraction,)
|
||||
frac_str = [frac_fmt % (f1,) for f1 in frac_part]
|
||||
|
||||
if factor == 1:
|
||||
if number_fraction is None:
|
||||
return [self.fmt_d % (s * int(v),) for s, v in zip(ss, values)]
|
||||
else:
|
||||
return [self.fmt_ds % (s * int(v), f1)
|
||||
for s, v, f1 in zip(ss, values, frac_str)]
|
||||
elif factor == 60:
|
||||
deg_part, min_part = divmod(values, 60)
|
||||
if number_fraction is None:
|
||||
return [self.fmt_d_m % (s1, d1, m1)
|
||||
for s1, d1, m1 in zip(signs, deg_part, min_part)]
|
||||
else:
|
||||
return [self.fmt_d_ms % (s, d1, m1, f1)
|
||||
for s, d1, m1, f1
|
||||
in zip(signs, deg_part, min_part, frac_str)]
|
||||
|
||||
elif factor == 3600:
|
||||
if ss[-1] == -1:
|
||||
inverse_order = True
|
||||
values = values[::-1]
|
||||
signs = signs[::-1]
|
||||
else:
|
||||
inverse_order = False
|
||||
|
||||
l_hm_old = ""
|
||||
r = []
|
||||
|
||||
deg_part, min_part_ = divmod(values, 3600)
|
||||
min_part, sec_part = divmod(min_part_, 60)
|
||||
|
||||
if number_fraction is None:
|
||||
sec_str = [self.fmt_s_partial % (s1,) for s1 in sec_part]
|
||||
else:
|
||||
sec_str = [self.fmt_ss_partial % (s1, f1)
|
||||
for s1, f1 in zip(sec_part, frac_str)]
|
||||
|
||||
for s, d1, m1, s1 in zip(signs, deg_part, min_part, sec_str):
|
||||
l_hm = self.fmt_d_m_partial % (s, d1, m1)
|
||||
if l_hm != l_hm_old:
|
||||
l_hm_old = l_hm
|
||||
l = l_hm + s1
|
||||
else:
|
||||
l = "$" + s + s1
|
||||
r.append(l)
|
||||
|
||||
if inverse_order:
|
||||
return r[::-1]
|
||||
else:
|
||||
return r
|
||||
|
||||
else: # factor > 3600.
|
||||
return [r"$%s^{\circ}$" % (str(v),) for v in ss*values]
|
||||
|
||||
|
||||
class FormatterHMS(FormatterDMS):
|
||||
deg_mark = r"^\mathrm{h}"
|
||||
min_mark = r"^\mathrm{m}"
|
||||
sec_mark = r"^\mathrm{s}"
|
||||
|
||||
fmt_d = "$%d" + deg_mark + "$"
|
||||
fmt_ds = r"$%d.%s" + deg_mark + "$"
|
||||
|
||||
# %s for sign
|
||||
fmt_d_m = r"$%s%d" + deg_mark + r"\,%02d" + min_mark+"$"
|
||||
fmt_d_ms = r"$%s%d" + deg_mark + r"\,%02d.%s" + min_mark+"$"
|
||||
|
||||
fmt_d_m_partial = "$%s%d" + deg_mark + r"\,%02d" + min_mark + r"\,"
|
||||
fmt_s_partial = "%02d" + sec_mark + "$"
|
||||
fmt_ss_partial = "%02d.%s" + sec_mark + "$"
|
||||
|
||||
def __call__(self, direction, factor, values): # hour
|
||||
return super().__call__(direction, factor, np.asarray(values) / 15)
|
||||
|
||||
|
||||
class ExtremeFinderCycle(ExtremeFinderSimple):
|
||||
# docstring inherited
|
||||
|
||||
def __init__(self, nx, ny,
|
||||
lon_cycle=360., lat_cycle=None,
|
||||
lon_minmax=None, lat_minmax=(-90, 90)):
|
||||
"""
|
||||
This subclass handles the case where one or both coordinates should be
|
||||
taken modulo 360, or be restricted to not exceed a specific range.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nx, ny : int
|
||||
The number of samples in each direction.
|
||||
|
||||
lon_cycle, lat_cycle : 360 or None
|
||||
If not None, values in the corresponding direction are taken modulo
|
||||
*lon_cycle* or *lat_cycle*; in theory this can be any number but
|
||||
the implementation actually assumes that it is 360 (if not None);
|
||||
other values give nonsensical results.
|
||||
|
||||
This is done by "unwrapping" the transformed grid coordinates so
|
||||
that jumps are less than a half-cycle; then normalizing the span to
|
||||
no more than a full cycle.
|
||||
|
||||
For example, if values are in the union of the [0, 2] and
|
||||
[358, 360] intervals (typically, angles measured modulo 360), the
|
||||
values in the second interval are normalized to [-2, 0] instead so
|
||||
that the values now cover [-2, 2]. If values are in a range of
|
||||
[5, 1000], this gets normalized to [5, 365].
|
||||
|
||||
lon_minmax, lat_minmax : (float, float) or None
|
||||
If not None, the computed bounding box is clipped to the given
|
||||
range in the corresponding direction.
|
||||
"""
|
||||
self.nx, self.ny = nx, ny
|
||||
self.lon_cycle, self.lat_cycle = lon_cycle, lat_cycle
|
||||
self.lon_minmax = lon_minmax
|
||||
self.lat_minmax = lat_minmax
|
||||
|
||||
def __call__(self, transform_xy, x1, y1, x2, y2):
|
||||
# docstring inherited
|
||||
x, y = np.meshgrid(
|
||||
np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny))
|
||||
lon, lat = transform_xy(np.ravel(x), np.ravel(y))
|
||||
|
||||
# iron out jumps, but algorithm should be improved.
|
||||
# This is just naive way of doing and my fail for some cases.
|
||||
# Consider replacing this with numpy.unwrap
|
||||
# We are ignoring invalid warnings. They are triggered when
|
||||
# comparing arrays with NaNs using > We are already handling
|
||||
# that correctly using np.nanmin and np.nanmax
|
||||
with np.errstate(invalid='ignore'):
|
||||
if self.lon_cycle is not None:
|
||||
lon0 = np.nanmin(lon)
|
||||
lon -= 360. * ((lon - lon0) > 180.)
|
||||
if self.lat_cycle is not None:
|
||||
lat0 = np.nanmin(lat)
|
||||
lat -= 360. * ((lat - lat0) > 180.)
|
||||
|
||||
lon_min, lon_max = np.nanmin(lon), np.nanmax(lon)
|
||||
lat_min, lat_max = np.nanmin(lat), np.nanmax(lat)
|
||||
|
||||
lon_min, lon_max, lat_min, lat_max = \
|
||||
self._add_pad(lon_min, lon_max, lat_min, lat_max)
|
||||
|
||||
# check cycle
|
||||
if self.lon_cycle:
|
||||
lon_max = min(lon_max, lon_min + self.lon_cycle)
|
||||
if self.lat_cycle:
|
||||
lat_max = min(lat_max, lat_min + self.lat_cycle)
|
||||
|
||||
if self.lon_minmax is not None:
|
||||
min0 = self.lon_minmax[0]
|
||||
lon_min = max(min0, lon_min)
|
||||
max0 = self.lon_minmax[1]
|
||||
lon_max = min(max0, lon_max)
|
||||
|
||||
if self.lat_minmax is not None:
|
||||
min0 = self.lat_minmax[0]
|
||||
lat_min = max(min0, lat_min)
|
||||
max0 = self.lat_minmax[1]
|
||||
lat_max = min(max0, lat_max)
|
||||
|
||||
return lon_min, lon_max, lat_min, lat_max
|
|
@ -0,0 +1,2 @@
|
|||
from mpl_toolkits.axes_grid1.axes_divider import (
|
||||
Divider, AxesLocator, SubplotDivider, AxesDivider, make_axes_locatable)
|
18
venv/Lib/site-packages/mpl_toolkits/axisartist/axes_grid.py
Normal file
18
venv/Lib/site-packages/mpl_toolkits/axisartist/axes_grid.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import mpl_toolkits.axes_grid1.axes_grid as axes_grid_orig
|
||||
from .axislines import Axes
|
||||
|
||||
|
||||
class CbarAxes(axes_grid_orig.CbarAxesBase, Axes):
|
||||
pass
|
||||
|
||||
|
||||
class Grid(axes_grid_orig.Grid):
|
||||
_defaultAxesClass = Axes
|
||||
|
||||
|
||||
class ImageGrid(axes_grid_orig.ImageGrid):
|
||||
_defaultAxesClass = Axes
|
||||
_defaultCbarAxesClass = CbarAxes
|
||||
|
||||
|
||||
AxesGrid = ImageGrid
|
|
@ -0,0 +1,7 @@
|
|||
from mpl_toolkits.axes_grid1.axes_rgb import (
|
||||
make_rgb_axes, imshow_rgb, RGBAxes as _RGBAxes)
|
||||
from .axislines import Axes
|
||||
|
||||
|
||||
class RGBAxes(_RGBAxes):
|
||||
_defaultAxesClass = Axes
|
1229
venv/Lib/site-packages/mpl_toolkits/axisartist/axis_artist.py
Normal file
1229
venv/Lib/site-packages/mpl_toolkits/axisartist/axis_artist.py
Normal file
File diff suppressed because it is too large
Load diff
153
venv/Lib/site-packages/mpl_toolkits/axisartist/axisline_style.py
Normal file
153
venv/Lib/site-packages/mpl_toolkits/axisartist/axisline_style.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
from matplotlib.patches import _Style, FancyArrowPatch
|
||||
from matplotlib.transforms import IdentityTransform
|
||||
from matplotlib.path import Path
|
||||
import numpy as np
|
||||
|
||||
|
||||
class _FancyAxislineStyle:
|
||||
class SimpleArrow(FancyArrowPatch):
|
||||
"""
|
||||
The artist class that will be returned for SimpleArrow style.
|
||||
"""
|
||||
_ARROW_STYLE = "->"
|
||||
|
||||
def __init__(self, axis_artist, line_path, transform,
|
||||
line_mutation_scale):
|
||||
self._axis_artist = axis_artist
|
||||
self._line_transform = transform
|
||||
self._line_path = line_path
|
||||
self._line_mutation_scale = line_mutation_scale
|
||||
|
||||
FancyArrowPatch.__init__(self,
|
||||
path=self._line_path,
|
||||
arrowstyle=self._ARROW_STYLE,
|
||||
patchA=None,
|
||||
patchB=None,
|
||||
shrinkA=0.,
|
||||
shrinkB=0.,
|
||||
mutation_scale=line_mutation_scale,
|
||||
mutation_aspect=None,
|
||||
transform=IdentityTransform(),
|
||||
)
|
||||
|
||||
def set_line_mutation_scale(self, scale):
|
||||
self.set_mutation_scale(scale*self._line_mutation_scale)
|
||||
|
||||
def _extend_path(self, path, mutation_size=10):
|
||||
"""
|
||||
Extend the path to make a room for drawing arrow.
|
||||
"""
|
||||
from matplotlib.bezier import get_cos_sin
|
||||
|
||||
x0, y0 = path.vertices[-2]
|
||||
x1, y1 = path.vertices[-1]
|
||||
cost, sint = get_cos_sin(x0, y0, x1, y1)
|
||||
|
||||
d = mutation_size * 1.
|
||||
x2, y2 = x1 + cost*d, y1+sint*d
|
||||
|
||||
if path.codes is None:
|
||||
_path = Path(np.concatenate([path.vertices, [[x2, y2]]]))
|
||||
else:
|
||||
_path = Path(np.concatenate([path.vertices, [[x2, y2]]]),
|
||||
np.concatenate([path.codes, [Path.LINETO]]))
|
||||
|
||||
return _path
|
||||
|
||||
def set_path(self, path):
|
||||
self._line_path = path
|
||||
|
||||
def draw(self, renderer):
|
||||
"""
|
||||
Draw the axis line.
|
||||
1) transform the path to the display coordinate.
|
||||
2) extend the path to make a room for arrow
|
||||
3) update the path of the FancyArrowPatch.
|
||||
4) draw
|
||||
"""
|
||||
path_in_disp = self._line_transform.transform_path(self._line_path)
|
||||
mutation_size = self.get_mutation_scale() # line_mutation_scale()
|
||||
extended_path = self._extend_path(path_in_disp,
|
||||
mutation_size=mutation_size)
|
||||
self._path_original = extended_path
|
||||
FancyArrowPatch.draw(self, renderer)
|
||||
|
||||
class FilledArrow(SimpleArrow):
|
||||
"""
|
||||
The artist class that will be returned for SimpleArrow style.
|
||||
"""
|
||||
_ARROW_STYLE = "-|>"
|
||||
|
||||
|
||||
class AxislineStyle(_Style):
|
||||
"""
|
||||
A container class which defines style classes for AxisArtists.
|
||||
|
||||
An instance of any axisline style class is an callable object,
|
||||
whose call signature is ::
|
||||
|
||||
__call__(self, axis_artist, path, transform)
|
||||
|
||||
When called, this should return an `.Artist` with the following methods::
|
||||
|
||||
def set_path(self, path):
|
||||
# set the path for axisline.
|
||||
|
||||
def set_line_mutation_scale(self, scale):
|
||||
# set the scale
|
||||
|
||||
def draw(self, renderer):
|
||||
# draw
|
||||
"""
|
||||
|
||||
_style_list = {}
|
||||
|
||||
class _Base:
|
||||
# The derived classes are required to be able to be initialized
|
||||
# w/o arguments, i.e., all its argument (except self) must have
|
||||
# the default values.
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
initialization.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
def __call__(self, axis_artist, transform):
|
||||
"""
|
||||
Given the AxisArtist instance, and transform for the path (set_path
|
||||
method), return the Matplotlib artist for drawing the axis line.
|
||||
"""
|
||||
return self.new_line(axis_artist, transform)
|
||||
|
||||
class SimpleArrow(_Base):
|
||||
"""
|
||||
A simple arrow.
|
||||
"""
|
||||
|
||||
ArrowAxisClass = _FancyAxislineStyle.SimpleArrow
|
||||
|
||||
def __init__(self, size=1):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
size : float
|
||||
Size of the arrow as a fraction of the ticklabel size.
|
||||
"""
|
||||
|
||||
self.size = size
|
||||
super().__init__()
|
||||
|
||||
def new_line(self, axis_artist, transform):
|
||||
|
||||
linepath = Path([(0, 0), (0, 1)])
|
||||
axisline = self.ArrowAxisClass(axis_artist, linepath, transform,
|
||||
line_mutation_scale=self.size)
|
||||
return axisline
|
||||
|
||||
_style_list["->"] = SimpleArrow
|
||||
|
||||
class FilledArrow(SimpleArrow):
|
||||
ArrowAxisClass = _FancyAxislineStyle.FilledArrow
|
||||
|
||||
_style_list["-|>"] = FilledArrow
|
607
venv/Lib/site-packages/mpl_toolkits/axisartist/axislines.py
Normal file
607
venv/Lib/site-packages/mpl_toolkits/axisartist/axislines.py
Normal file
|
@ -0,0 +1,607 @@
|
|||
"""
|
||||
Axislines includes modified implementation of the Axes class. The
|
||||
biggest difference is that the artists responsible for drawing the axis spine,
|
||||
ticks, ticklabels and axis labels are separated out from Matplotlib's Axis
|
||||
class. Originally, this change was motivated to support curvilinear
|
||||
grid. Here are a few reasons that I came up with a new axes class:
|
||||
|
||||
* "top" and "bottom" x-axis (or "left" and "right" y-axis) can have
|
||||
different ticks (tick locations and labels). This is not possible
|
||||
with the current Matplotlib, although some twin axes trick can help.
|
||||
|
||||
* Curvilinear grid.
|
||||
|
||||
* angled ticks.
|
||||
|
||||
In the new axes class, xaxis and yaxis is set to not visible by
|
||||
default, and new set of artist (AxisArtist) are defined to draw axis
|
||||
line, ticks, ticklabels and axis label. Axes.axis attribute serves as
|
||||
a dictionary of these artists, i.e., ax.axis["left"] is a AxisArtist
|
||||
instance responsible to draw left y-axis. The default Axes.axis contains
|
||||
"bottom", "left", "top" and "right".
|
||||
|
||||
AxisArtist can be considered as a container artist and
|
||||
has following children artists which will draw ticks, labels, etc.
|
||||
|
||||
* line
|
||||
* major_ticks, major_ticklabels
|
||||
* minor_ticks, minor_ticklabels
|
||||
* offsetText
|
||||
* label
|
||||
|
||||
Note that these are separate artists from `matplotlib.axis.Axis`, thus most
|
||||
tick-related functions in Matplotlib won't work. For example, color and
|
||||
markerwidth of the ``ax.axis["bottom"].major_ticks`` will follow those of
|
||||
Axes.xaxis unless explicitly specified.
|
||||
|
||||
In addition to AxisArtist, the Axes will have *gridlines* attribute,
|
||||
which obviously draws grid lines. The gridlines needs to be separated
|
||||
from the axis as some gridlines can never pass any axis.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import cbook, rcParams
|
||||
import matplotlib.axes as maxes
|
||||
from matplotlib.path import Path
|
||||
from mpl_toolkits.axes_grid1 import mpl_axes
|
||||
from .axisline_style import AxislineStyle
|
||||
from .axis_artist import AxisArtist, GridlinesCollection
|
||||
|
||||
|
||||
class AxisArtistHelper:
|
||||
"""
|
||||
AxisArtistHelper should define
|
||||
following method with given APIs. Note that the first axes argument
|
||||
will be axes attribute of the caller artist.::
|
||||
|
||||
|
||||
# LINE (spinal line?)
|
||||
|
||||
def get_line(self, axes):
|
||||
# path : Path
|
||||
return path
|
||||
|
||||
def get_line_transform(self, axes):
|
||||
# ...
|
||||
# trans : transform
|
||||
return trans
|
||||
|
||||
# LABEL
|
||||
|
||||
def get_label_pos(self, axes):
|
||||
# x, y : position
|
||||
return (x, y), trans
|
||||
|
||||
|
||||
def get_label_offset_transform(self,
|
||||
axes,
|
||||
pad_points, fontprops, renderer,
|
||||
bboxes,
|
||||
):
|
||||
# va : vertical alignment
|
||||
# ha : horizontal alignment
|
||||
# a : angle
|
||||
return trans, va, ha, a
|
||||
|
||||
# TICK
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return trans
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
# iter : iterable object that yields (c, angle, l) where
|
||||
# c, angle, l is position, tick angle, and label
|
||||
|
||||
return iter_major, iter_minor
|
||||
"""
|
||||
|
||||
class _Base:
|
||||
"""Base class for axis helper."""
|
||||
def __init__(self):
|
||||
self.delta1, self.delta2 = 0.00001, 0.00001
|
||||
|
||||
def update_lim(self, axes):
|
||||
pass
|
||||
|
||||
class Fixed(_Base):
|
||||
"""Helper class for a fixed (in the axes coordinate) axis."""
|
||||
|
||||
_default_passthru_pt = dict(left=(0, 0),
|
||||
right=(1, 0),
|
||||
bottom=(0, 0),
|
||||
top=(0, 1))
|
||||
|
||||
def __init__(self, loc, nth_coord=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies
|
||||
in 2d, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
cbook._check_in_list(["left", "right", "bottom", "top"], loc=loc)
|
||||
self._loc = loc
|
||||
|
||||
if nth_coord is None:
|
||||
if loc in ["left", "right"]:
|
||||
nth_coord = 1
|
||||
elif loc in ["bottom", "top"]:
|
||||
nth_coord = 0
|
||||
|
||||
self.nth_coord = nth_coord
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.passthru_pt = self._default_passthru_pt[loc]
|
||||
|
||||
_verts = np.array([[0., 0.],
|
||||
[1., 1.]])
|
||||
fixed_coord = 1 - nth_coord
|
||||
_verts[:, fixed_coord] = self.passthru_pt[fixed_coord]
|
||||
|
||||
# axis line in transAxes
|
||||
self._path = Path(_verts)
|
||||
|
||||
def get_nth_coord(self):
|
||||
return self.nth_coord
|
||||
|
||||
# LINE
|
||||
|
||||
def get_line(self, axes):
|
||||
return self._path
|
||||
|
||||
def get_line_transform(self, axes):
|
||||
return axes.transAxes
|
||||
|
||||
# LABEL
|
||||
|
||||
def get_axislabel_transform(self, axes):
|
||||
return axes.transAxes
|
||||
|
||||
def get_axislabel_pos_angle(self, axes):
|
||||
"""
|
||||
Return the label reference position in transAxes.
|
||||
|
||||
get_label_transform() returns a transform of (transAxes+offset)
|
||||
"""
|
||||
return dict(left=((0., 0.5), 90), # (position, angle_tangent)
|
||||
right=((1., 0.5), 90),
|
||||
bottom=((0.5, 0.), 0),
|
||||
top=((0.5, 1.), 0))[self._loc]
|
||||
|
||||
# TICK
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return [axes.get_xaxis_transform(),
|
||||
axes.get_yaxis_transform()][self.nth_coord]
|
||||
|
||||
class Floating(_Base):
|
||||
|
||||
def __init__(self, nth_coord, value):
|
||||
self.nth_coord = nth_coord
|
||||
self._value = value
|
||||
super().__init__()
|
||||
|
||||
def get_nth_coord(self):
|
||||
return self.nth_coord
|
||||
|
||||
def get_line(self, axes):
|
||||
raise RuntimeError(
|
||||
"get_line method should be defined by the derived class")
|
||||
|
||||
|
||||
class AxisArtistHelperRectlinear:
|
||||
|
||||
class Fixed(AxisArtistHelper.Fixed):
|
||||
|
||||
def __init__(self, axes, loc, nth_coord=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies
|
||||
in 2d, nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
super().__init__(loc, nth_coord)
|
||||
self.axis = [axes.xaxis, axes.yaxis][self.nth_coord]
|
||||
|
||||
# TICK
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label"""
|
||||
|
||||
loc = self._loc
|
||||
|
||||
if loc in ["bottom", "top"]:
|
||||
angle_normal, angle_tangent = 90, 0
|
||||
else:
|
||||
angle_normal, angle_tangent = 0, 90
|
||||
|
||||
major = self.axis.major
|
||||
majorLocs = major.locator()
|
||||
majorLabels = major.formatter.format_ticks(majorLocs)
|
||||
|
||||
minor = self.axis.minor
|
||||
minorLocs = minor.locator()
|
||||
minorLabels = minor.formatter.format_ticks(minorLocs)
|
||||
|
||||
tick_to_axes = self.get_tick_transform(axes) - axes.transAxes
|
||||
|
||||
def _f(locs, labels):
|
||||
for x, l in zip(locs, labels):
|
||||
|
||||
c = list(self.passthru_pt) # copy
|
||||
c[self.nth_coord] = x
|
||||
|
||||
# check if the tick point is inside axes
|
||||
c2 = tick_to_axes.transform(c)
|
||||
if (0 - self.delta1
|
||||
<= c2[self.nth_coord]
|
||||
<= 1 + self.delta2):
|
||||
yield c, angle_normal, angle_tangent, l
|
||||
|
||||
return _f(majorLocs, majorLabels), _f(minorLocs, minorLabels)
|
||||
|
||||
class Floating(AxisArtistHelper.Floating):
|
||||
def __init__(self, axes, nth_coord,
|
||||
passingthrough_point, axis_direction="bottom"):
|
||||
super().__init__(nth_coord, passingthrough_point)
|
||||
self._axis_direction = axis_direction
|
||||
self.axis = [axes.xaxis, axes.yaxis][self.nth_coord]
|
||||
|
||||
def get_line(self, axes):
|
||||
_verts = np.array([[0., 0.],
|
||||
[1., 1.]])
|
||||
|
||||
fixed_coord = 1 - self.nth_coord
|
||||
data_to_axes = axes.transData - axes.transAxes
|
||||
p = data_to_axes.transform([self._value, self._value])
|
||||
_verts[:, fixed_coord] = p[fixed_coord]
|
||||
|
||||
return Path(_verts)
|
||||
|
||||
def get_line_transform(self, axes):
|
||||
return axes.transAxes
|
||||
|
||||
def get_axislabel_transform(self, axes):
|
||||
return axes.transAxes
|
||||
|
||||
def get_axislabel_pos_angle(self, axes):
|
||||
"""
|
||||
Return the label reference position in transAxes.
|
||||
|
||||
get_label_transform() returns a transform of (transAxes+offset)
|
||||
"""
|
||||
angle = [0, 90][self.nth_coord]
|
||||
_verts = [0.5, 0.5]
|
||||
fixed_coord = 1 - self.nth_coord
|
||||
data_to_axes = axes.transData - axes.transAxes
|
||||
p = data_to_axes.transform([self._value, self._value])
|
||||
_verts[fixed_coord] = p[fixed_coord]
|
||||
if 0 <= _verts[fixed_coord] <= 1:
|
||||
return _verts, angle
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return axes.transData
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label"""
|
||||
if self.nth_coord == 0:
|
||||
angle_normal, angle_tangent = 90, 0
|
||||
else:
|
||||
angle_normal, angle_tangent = 0, 90
|
||||
|
||||
major = self.axis.major
|
||||
majorLocs = major.locator()
|
||||
majorLabels = major.formatter.format_ticks(majorLocs)
|
||||
|
||||
minor = self.axis.minor
|
||||
minorLocs = minor.locator()
|
||||
minorLabels = minor.formatter.format_ticks(minorLocs)
|
||||
|
||||
data_to_axes = axes.transData - axes.transAxes
|
||||
|
||||
def _f(locs, labels):
|
||||
for x, l in zip(locs, labels):
|
||||
c = [self._value, self._value]
|
||||
c[self.nth_coord] = x
|
||||
c1, c2 = data_to_axes.transform(c)
|
||||
if (0 <= c1 <= 1 and 0 <= c2 <= 1
|
||||
and 0 - self.delta1
|
||||
<= [c1, c2][self.nth_coord]
|
||||
<= 1 + self.delta2):
|
||||
yield c, angle_normal, angle_tangent, l
|
||||
|
||||
return _f(majorLocs, majorLabels), _f(minorLocs, minorLabels)
|
||||
|
||||
|
||||
class GridHelperBase:
|
||||
|
||||
def __init__(self):
|
||||
self._force_update = True
|
||||
self._old_limits = None
|
||||
super().__init__()
|
||||
|
||||
def update_lim(self, axes):
|
||||
x1, x2 = axes.get_xlim()
|
||||
y1, y2 = axes.get_ylim()
|
||||
|
||||
if self._force_update or self._old_limits != (x1, x2, y1, y2):
|
||||
self._update(x1, x2, y1, y2)
|
||||
self._force_update = False
|
||||
self._old_limits = (x1, x2, y1, y2)
|
||||
|
||||
def _update(self, x1, x2, y1, y2):
|
||||
pass
|
||||
|
||||
def invalidate(self):
|
||||
self._force_update = True
|
||||
|
||||
def valid(self):
|
||||
return not self._force_update
|
||||
|
||||
def get_gridlines(self, which, axis):
|
||||
"""
|
||||
Return list of grid lines as a list of paths (list of points).
|
||||
|
||||
*which* : "major" or "minor"
|
||||
*axis* : "both", "x" or "y"
|
||||
"""
|
||||
return []
|
||||
|
||||
def new_gridlines(self, ax):
|
||||
"""
|
||||
Create and return a new GridlineCollection instance.
|
||||
|
||||
*which* : "major" or "minor"
|
||||
*axis* : "both", "x" or "y"
|
||||
|
||||
"""
|
||||
gridlines = GridlinesCollection(None, transform=ax.transData,
|
||||
colors=rcParams['grid.color'],
|
||||
linestyles=rcParams['grid.linestyle'],
|
||||
linewidths=rcParams['grid.linewidth'])
|
||||
ax._set_artist_props(gridlines)
|
||||
gridlines.set_grid_helper(self)
|
||||
|
||||
ax.axes._set_artist_props(gridlines)
|
||||
# gridlines.set_clip_path(self.axes.patch)
|
||||
# set_clip_path need to be deferred after Axes.cla is completed.
|
||||
# It is done inside the cla.
|
||||
|
||||
return gridlines
|
||||
|
||||
|
||||
class GridHelperRectlinear(GridHelperBase):
|
||||
|
||||
def __init__(self, axes):
|
||||
super().__init__()
|
||||
self.axes = axes
|
||||
|
||||
def new_fixed_axis(self, loc,
|
||||
nth_coord=None,
|
||||
axis_direction=None,
|
||||
offset=None,
|
||||
axes=None,
|
||||
):
|
||||
|
||||
if axes is None:
|
||||
cbook._warn_external(
|
||||
"'new_fixed_axis' explicitly requires the axes keyword.")
|
||||
axes = self.axes
|
||||
|
||||
_helper = AxisArtistHelperRectlinear.Fixed(axes, loc, nth_coord)
|
||||
|
||||
if axis_direction is None:
|
||||
axis_direction = loc
|
||||
axisline = AxisArtist(axes, _helper, offset=offset,
|
||||
axis_direction=axis_direction,
|
||||
)
|
||||
|
||||
return axisline
|
||||
|
||||
def new_floating_axis(self, nth_coord, value,
|
||||
axis_direction="bottom",
|
||||
axes=None,
|
||||
):
|
||||
|
||||
if axes is None:
|
||||
cbook._warn_external(
|
||||
"'new_floating_axis' explicitly requires the axes keyword.")
|
||||
axes = self.axes
|
||||
|
||||
_helper = AxisArtistHelperRectlinear.Floating(
|
||||
axes, nth_coord, value, axis_direction)
|
||||
|
||||
axisline = AxisArtist(axes, _helper)
|
||||
|
||||
axisline.line.set_clip_on(True)
|
||||
axisline.line.set_clip_box(axisline.axes.bbox)
|
||||
return axisline
|
||||
|
||||
def get_gridlines(self, which="major", axis="both"):
|
||||
"""
|
||||
Return list of gridline coordinates in data coordinates.
|
||||
|
||||
*which* : "major" or "minor"
|
||||
*axis* : "both", "x" or "y"
|
||||
"""
|
||||
gridlines = []
|
||||
|
||||
if axis in ["both", "x"]:
|
||||
locs = []
|
||||
y1, y2 = self.axes.get_ylim()
|
||||
if which in ["both", "major"]:
|
||||
locs.extend(self.axes.xaxis.major.locator())
|
||||
if which in ["both", "minor"]:
|
||||
locs.extend(self.axes.xaxis.minor.locator())
|
||||
|
||||
for x in locs:
|
||||
gridlines.append([[x, x], [y1, y2]])
|
||||
|
||||
if axis in ["both", "y"]:
|
||||
x1, x2 = self.axes.get_xlim()
|
||||
locs = []
|
||||
if self.axes.yaxis._gridOnMajor:
|
||||
locs.extend(self.axes.yaxis.major.locator())
|
||||
if self.axes.yaxis._gridOnMinor:
|
||||
locs.extend(self.axes.yaxis.minor.locator())
|
||||
|
||||
for y in locs:
|
||||
gridlines.append([[x1, x2], [y, y]])
|
||||
|
||||
return gridlines
|
||||
|
||||
|
||||
class Axes(maxes.Axes):
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return maxes.Axes.axis(self.axes, *args, **kwargs)
|
||||
|
||||
def __init__(self, *args, grid_helper=None, **kwargs):
|
||||
self._axisline_on = True
|
||||
self._grid_helper = (grid_helper if grid_helper
|
||||
else GridHelperRectlinear(self))
|
||||
super().__init__(*args, **kwargs)
|
||||
self.toggle_axisline(True)
|
||||
|
||||
def toggle_axisline(self, b=None):
|
||||
if b is None:
|
||||
b = not self._axisline_on
|
||||
if b:
|
||||
self._axisline_on = True
|
||||
for s in self.spines.values():
|
||||
s.set_visible(False)
|
||||
self.xaxis.set_visible(False)
|
||||
self.yaxis.set_visible(False)
|
||||
else:
|
||||
self._axisline_on = False
|
||||
for s in self.spines.values():
|
||||
s.set_visible(True)
|
||||
self.xaxis.set_visible(True)
|
||||
self.yaxis.set_visible(True)
|
||||
|
||||
def _init_axis_artists(self, axes=None):
|
||||
if axes is None:
|
||||
axes = self
|
||||
|
||||
self._axislines = mpl_axes.Axes.AxisDict(self)
|
||||
new_fixed_axis = self.get_grid_helper().new_fixed_axis
|
||||
for loc in ["bottom", "top", "left", "right"]:
|
||||
self._axislines[loc] = new_fixed_axis(loc=loc, axes=axes,
|
||||
axis_direction=loc)
|
||||
|
||||
for axisline in [self._axislines["top"], self._axislines["right"]]:
|
||||
axisline.label.set_visible(False)
|
||||
axisline.major_ticklabels.set_visible(False)
|
||||
axisline.minor_ticklabels.set_visible(False)
|
||||
|
||||
@property
|
||||
def axis(self):
|
||||
return self._axislines
|
||||
|
||||
def new_gridlines(self, grid_helper=None):
|
||||
"""
|
||||
Create and return a new GridlineCollection instance.
|
||||
|
||||
*which* : "major" or "minor"
|
||||
*axis* : "both", "x" or "y"
|
||||
|
||||
"""
|
||||
if grid_helper is None:
|
||||
grid_helper = self.get_grid_helper()
|
||||
|
||||
gridlines = grid_helper.new_gridlines(self)
|
||||
return gridlines
|
||||
|
||||
def _init_gridlines(self, grid_helper=None):
|
||||
# It is done inside the cla.
|
||||
self.gridlines = self.new_gridlines(grid_helper)
|
||||
|
||||
def cla(self):
|
||||
# gridlines need to b created before cla() since cla calls grid()
|
||||
self._init_gridlines()
|
||||
super().cla()
|
||||
|
||||
# the clip_path should be set after Axes.cla() since that's
|
||||
# when a patch is created.
|
||||
self.gridlines.set_clip_path(self.axes.patch)
|
||||
|
||||
self._init_axis_artists()
|
||||
|
||||
def get_grid_helper(self):
|
||||
return self._grid_helper
|
||||
|
||||
def grid(self, b=None, which='major', axis="both", **kwargs):
|
||||
"""
|
||||
Toggle the gridlines, and optionally set the properties of the lines.
|
||||
"""
|
||||
# their are some discrepancy between the behavior of grid in
|
||||
# axes_grid and the original mpl's grid, because axes_grid
|
||||
# explicitly set the visibility of the gridlines.
|
||||
super().grid(b, which=which, axis=axis, **kwargs)
|
||||
if not self._axisline_on:
|
||||
return
|
||||
if b is None:
|
||||
b = (self.axes.xaxis._gridOnMinor
|
||||
or self.axes.xaxis._gridOnMajor
|
||||
or self.axes.yaxis._gridOnMinor
|
||||
or self.axes.yaxis._gridOnMajor)
|
||||
self.gridlines.set(which=which, axis=axis, visible=b)
|
||||
self.gridlines.set(**kwargs)
|
||||
|
||||
def get_children(self):
|
||||
if self._axisline_on:
|
||||
children = [*self._axislines.values(), self.gridlines]
|
||||
else:
|
||||
children = []
|
||||
children.extend(super().get_children())
|
||||
return children
|
||||
|
||||
def invalidate_grid_helper(self):
|
||||
self._grid_helper.invalidate()
|
||||
|
||||
def new_fixed_axis(self, loc, offset=None):
|
||||
gh = self.get_grid_helper()
|
||||
axis = gh.new_fixed_axis(loc,
|
||||
nth_coord=None,
|
||||
axis_direction=None,
|
||||
offset=offset,
|
||||
axes=self,
|
||||
)
|
||||
return axis
|
||||
|
||||
def new_floating_axis(self, nth_coord, value, axis_direction="bottom"):
|
||||
gh = self.get_grid_helper()
|
||||
axis = gh.new_floating_axis(nth_coord, value,
|
||||
axis_direction=axis_direction,
|
||||
axes=self)
|
||||
return axis
|
||||
|
||||
|
||||
Subplot = maxes.subplot_class_factory(Axes)
|
||||
|
||||
|
||||
class AxesZero(Axes):
|
||||
|
||||
def _init_axis_artists(self):
|
||||
super()._init_axis_artists()
|
||||
|
||||
new_floating_axis = self._grid_helper.new_floating_axis
|
||||
xaxis_zero = new_floating_axis(nth_coord=0,
|
||||
value=0.,
|
||||
axis_direction="bottom",
|
||||
axes=self)
|
||||
|
||||
xaxis_zero.line.set_clip_path(self.patch)
|
||||
xaxis_zero.set_visible(False)
|
||||
self._axislines["xzero"] = xaxis_zero
|
||||
|
||||
yaxis_zero = new_floating_axis(nth_coord=1,
|
||||
value=0.,
|
||||
axis_direction="left",
|
||||
axes=self)
|
||||
|
||||
yaxis_zero.line.set_clip_path(self.patch)
|
||||
yaxis_zero.set_visible(False)
|
||||
self._axislines["yzero"] = yaxis_zero
|
||||
|
||||
|
||||
SubplotZero = maxes.subplot_class_factory(AxesZero)
|
118
venv/Lib/site-packages/mpl_toolkits/axisartist/clip_path.py
Normal file
118
venv/Lib/site-packages/mpl_toolkits/axisartist/clip_path.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
import numpy as np
|
||||
from math import degrees
|
||||
from matplotlib import cbook
|
||||
import math
|
||||
|
||||
|
||||
def atan2(dy, dx):
|
||||
if dx == 0 and dy == 0:
|
||||
cbook._warn_external("dx and dy are 0")
|
||||
return 0
|
||||
else:
|
||||
return math.atan2(dy, dx)
|
||||
|
||||
|
||||
# FIXME : The current algorithm seems to return incorrect angle when the line
|
||||
# ends at the boundary.
|
||||
def clip(xlines, ylines, x0, clip="right", xdir=True, ydir=True):
|
||||
|
||||
clipped_xlines = []
|
||||
clipped_ylines = []
|
||||
|
||||
_pos_angles = []
|
||||
|
||||
xsign = 1 if xdir else -1
|
||||
ysign = 1 if ydir else -1
|
||||
|
||||
for x, y in zip(xlines, ylines):
|
||||
|
||||
if clip in ["up", "right"]:
|
||||
b = (x < x0).astype("i")
|
||||
db = b[1:] - b[:-1]
|
||||
else:
|
||||
b = (x > x0).astype("i")
|
||||
db = b[1:] - b[:-1]
|
||||
|
||||
if b[0]:
|
||||
ns = 0
|
||||
else:
|
||||
ns = -1
|
||||
segx, segy = [], []
|
||||
for (i,) in np.argwhere(db):
|
||||
c = db[i]
|
||||
if c == -1:
|
||||
dx = (x0 - x[i])
|
||||
dy = (y[i+1] - y[i]) * (dx / (x[i+1] - x[i]))
|
||||
y0 = y[i] + dy
|
||||
clipped_xlines.append(np.concatenate([segx, x[ns:i+1], [x0]]))
|
||||
clipped_ylines.append(np.concatenate([segy, y[ns:i+1], [y0]]))
|
||||
ns = -1
|
||||
segx, segy = [], []
|
||||
|
||||
if dx == 0. and dy == 0:
|
||||
dx = x[i+1] - x[i]
|
||||
dy = y[i+1] - y[i]
|
||||
|
||||
a = degrees(atan2(ysign*dy, xsign*dx))
|
||||
_pos_angles.append((x0, y0, a))
|
||||
|
||||
elif c == 1:
|
||||
dx = (x0 - x[i])
|
||||
dy = (y[i+1] - y[i]) * (dx / (x[i+1] - x[i]))
|
||||
y0 = y[i] + dy
|
||||
segx, segy = [x0], [y0]
|
||||
ns = i+1
|
||||
|
||||
if dx == 0. and dy == 0:
|
||||
dx = x[i+1] - x[i]
|
||||
dy = y[i+1] - y[i]
|
||||
|
||||
a = degrees(atan2(ysign*dy, xsign*dx))
|
||||
_pos_angles.append((x0, y0, a))
|
||||
|
||||
if ns != -1:
|
||||
clipped_xlines.append(np.concatenate([segx, x[ns:]]))
|
||||
clipped_ylines.append(np.concatenate([segy, y[ns:]]))
|
||||
|
||||
return clipped_xlines, clipped_ylines, _pos_angles
|
||||
|
||||
|
||||
def clip_line_to_rect(xline, yline, bbox):
|
||||
|
||||
x0, y0, x1, y1 = bbox.extents
|
||||
|
||||
xdir = x1 > x0
|
||||
ydir = y1 > y0
|
||||
|
||||
if x1 > x0:
|
||||
lx1, ly1, c_right_ = clip([xline], [yline], x1,
|
||||
clip="right", xdir=xdir, ydir=ydir)
|
||||
lx2, ly2, c_left_ = clip(lx1, ly1, x0,
|
||||
clip="left", xdir=xdir, ydir=ydir)
|
||||
else:
|
||||
lx1, ly1, c_right_ = clip([xline], [yline], x0,
|
||||
clip="right", xdir=xdir, ydir=ydir)
|
||||
lx2, ly2, c_left_ = clip(lx1, ly1, x1,
|
||||
clip="left", xdir=xdir, ydir=ydir)
|
||||
|
||||
if y1 > y0:
|
||||
ly3, lx3, c_top_ = clip(ly2, lx2, y1,
|
||||
clip="right", xdir=ydir, ydir=xdir)
|
||||
ly4, lx4, c_bottom_ = clip(ly3, lx3, y0,
|
||||
clip="left", xdir=ydir, ydir=xdir)
|
||||
else:
|
||||
ly3, lx3, c_top_ = clip(ly2, lx2, y0,
|
||||
clip="right", xdir=ydir, ydir=xdir)
|
||||
ly4, lx4, c_bottom_ = clip(ly3, lx3, y1,
|
||||
clip="left", xdir=ydir, ydir=xdir)
|
||||
|
||||
c_left = [((x, y), (a + 90) % 180 - 90) for x, y, a in c_left_
|
||||
if bbox.containsy(y)]
|
||||
c_bottom = [((x, y), (90 - a) % 180) for y, x, a in c_bottom_
|
||||
if bbox.containsx(x)]
|
||||
c_right = [((x, y), (a + 90) % 180 + 90) for x, y, a in c_right_
|
||||
if bbox.containsy(y)]
|
||||
c_top = [((x, y), (90 - a) % 180 + 180) for y, x, a in c_top_
|
||||
if bbox.containsx(x)]
|
||||
|
||||
return list(zip(lx4, ly4)), [c_left, c_bottom, c_right, c_top]
|
372
venv/Lib/site-packages/mpl_toolkits/axisartist/floating_axes.py
Normal file
372
venv/Lib/site-packages/mpl_toolkits/axisartist/floating_axes.py
Normal file
|
@ -0,0 +1,372 @@
|
|||
"""
|
||||
An experimental support for curvilinear grid.
|
||||
"""
|
||||
|
||||
# TODO :
|
||||
# see if tick_iterator method can be simplified by reusing the parent method.
|
||||
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
|
||||
import matplotlib.patches as mpatches
|
||||
from matplotlib.path import Path
|
||||
import matplotlib.axes as maxes
|
||||
|
||||
from mpl_toolkits.axes_grid1.parasite_axes import host_axes_class_factory
|
||||
|
||||
from . import axislines, grid_helper_curvelinear
|
||||
from .axis_artist import AxisArtist
|
||||
from .grid_finder import ExtremeFinderSimple
|
||||
|
||||
|
||||
class FloatingAxisArtistHelper(
|
||||
grid_helper_curvelinear.FloatingAxisArtistHelper):
|
||||
pass
|
||||
|
||||
|
||||
class FixedAxisArtistHelper(grid_helper_curvelinear.FloatingAxisArtistHelper):
|
||||
|
||||
def __init__(self, grid_helper, side, nth_coord_ticks=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies.
|
||||
nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
value, nth_coord = grid_helper.get_data_boundary(side)
|
||||
super().__init__(grid_helper, nth_coord, value, axis_direction=side)
|
||||
if nth_coord_ticks is None:
|
||||
nth_coord_ticks = nth_coord
|
||||
self.nth_coord_ticks = nth_coord_ticks
|
||||
|
||||
self.value = value
|
||||
self.grid_helper = grid_helper
|
||||
self._side = side
|
||||
|
||||
def update_lim(self, axes):
|
||||
self.grid_helper.update_lim(axes)
|
||||
self.grid_info = self.grid_helper.grid_info
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label, (optionally) tick_label"""
|
||||
|
||||
grid_finder = self.grid_helper.grid_finder
|
||||
|
||||
lat_levs, lat_n, lat_factor = self.grid_info["lat_info"]
|
||||
lon_levs, lon_n, lon_factor = self.grid_info["lon_info"]
|
||||
|
||||
lon_levs, lat_levs = np.asarray(lon_levs), np.asarray(lat_levs)
|
||||
if lat_factor is not None:
|
||||
yy0 = lat_levs / lat_factor
|
||||
dy = 0.001 / lat_factor
|
||||
else:
|
||||
yy0 = lat_levs
|
||||
dy = 0.001
|
||||
|
||||
if lon_factor is not None:
|
||||
xx0 = lon_levs / lon_factor
|
||||
dx = 0.001 / lon_factor
|
||||
else:
|
||||
xx0 = lon_levs
|
||||
dx = 0.001
|
||||
|
||||
extremes = self.grid_helper._extremes
|
||||
xmin, xmax = sorted(extremes[:2])
|
||||
ymin, ymax = sorted(extremes[2:])
|
||||
|
||||
def transform_xy(x, y):
|
||||
x1, y1 = grid_finder.transform_xy(x, y)
|
||||
x2, y2 = axes.transData.transform(np.array([x1, y1]).T).T
|
||||
return x2, y2
|
||||
|
||||
if self.nth_coord == 0:
|
||||
mask = (ymin <= yy0) & (yy0 <= ymax)
|
||||
yy0 = yy0[mask]
|
||||
xx0 = np.full_like(yy0, self.value)
|
||||
xx1, yy1 = transform_xy(xx0, yy0)
|
||||
|
||||
xx00 = xx0.astype(float, copy=True)
|
||||
xx00[xx0 + dx > xmax] -= dx
|
||||
xx1a, yy1a = transform_xy(xx00, yy0)
|
||||
xx1b, yy1b = transform_xy(xx00 + dx, yy0)
|
||||
|
||||
yy00 = yy0.astype(float, copy=True)
|
||||
yy00[yy0 + dy > ymax] -= dy
|
||||
xx2a, yy2a = transform_xy(xx0, yy00)
|
||||
xx2b, yy2b = transform_xy(xx0, yy00 + dy)
|
||||
|
||||
labels = self.grid_info["lat_labels"]
|
||||
labels = [l for l, m in zip(labels, mask) if m]
|
||||
|
||||
elif self.nth_coord == 1:
|
||||
mask = (xmin <= xx0) & (xx0 <= xmax)
|
||||
xx0 = xx0[mask]
|
||||
yy0 = np.full_like(xx0, self.value)
|
||||
xx1, yy1 = transform_xy(xx0, yy0)
|
||||
|
||||
yy00 = yy0.astype(float, copy=True)
|
||||
yy00[yy0 + dy > ymax] -= dy
|
||||
xx1a, yy1a = transform_xy(xx0, yy00)
|
||||
xx1b, yy1b = transform_xy(xx0, yy00 + dy)
|
||||
|
||||
xx00 = xx0.astype(float, copy=True)
|
||||
xx00[xx0 + dx > xmax] -= dx
|
||||
xx2a, yy2a = transform_xy(xx00, yy0)
|
||||
xx2b, yy2b = transform_xy(xx00 + dx, yy0)
|
||||
|
||||
labels = self.grid_info["lon_labels"]
|
||||
labels = [l for l, m in zip(labels, mask) if m]
|
||||
|
||||
def f1():
|
||||
dd = np.arctan2(yy1b - yy1a, xx1b - xx1a) # angle normal
|
||||
dd2 = np.arctan2(yy2b - yy2a, xx2b - xx2a) # angle tangent
|
||||
mm = (yy1b - yy1a == 0) & (xx1b - xx1a == 0) # mask not defined dd
|
||||
dd[mm] = dd2[mm] + np.pi / 2
|
||||
|
||||
tick_to_axes = self.get_tick_transform(axes) - axes.transAxes
|
||||
for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels):
|
||||
c2 = tick_to_axes.transform((x, y))
|
||||
delta = 0.00001
|
||||
if 0-delta <= c2[0] <= 1+delta and 0-delta <= c2[1] <= 1+delta:
|
||||
d1, d2 = np.rad2deg([d, d2])
|
||||
yield [x, y], d1, d2, lab
|
||||
|
||||
return f1(), iter([])
|
||||
|
||||
def get_line(self, axes):
|
||||
self.update_lim(axes)
|
||||
k, v = dict(left=("lon_lines0", 0),
|
||||
right=("lon_lines0", 1),
|
||||
bottom=("lat_lines0", 0),
|
||||
top=("lat_lines0", 1))[self._side]
|
||||
xx, yy = self.grid_info[k][v]
|
||||
return Path(np.column_stack([xx, yy]))
|
||||
|
||||
|
||||
class ExtremeFinderFixed(ExtremeFinderSimple):
|
||||
# docstring inherited
|
||||
|
||||
def __init__(self, extremes):
|
||||
"""
|
||||
This subclass always returns the same bounding box.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
extremes : (float, float, float, float)
|
||||
The bounding box that this helper always returns.
|
||||
"""
|
||||
self._extremes = extremes
|
||||
|
||||
def __call__(self, transform_xy, x1, y1, x2, y2):
|
||||
# docstring inherited
|
||||
return self._extremes
|
||||
|
||||
|
||||
class GridHelperCurveLinear(grid_helper_curvelinear.GridHelperCurveLinear):
|
||||
|
||||
def __init__(self, aux_trans, extremes,
|
||||
grid_locator1=None,
|
||||
grid_locator2=None,
|
||||
tick_formatter1=None,
|
||||
tick_formatter2=None):
|
||||
# docstring inherited
|
||||
self._extremes = extremes
|
||||
extreme_finder = ExtremeFinderFixed(extremes)
|
||||
super().__init__(aux_trans,
|
||||
extreme_finder,
|
||||
grid_locator1=grid_locator1,
|
||||
grid_locator2=grid_locator2,
|
||||
tick_formatter1=tick_formatter1,
|
||||
tick_formatter2=tick_formatter2)
|
||||
|
||||
def get_data_boundary(self, side):
|
||||
"""
|
||||
Return v=0, nth=1.
|
||||
"""
|
||||
lon1, lon2, lat1, lat2 = self._extremes
|
||||
return dict(left=(lon1, 0),
|
||||
right=(lon2, 0),
|
||||
bottom=(lat1, 1),
|
||||
top=(lat2, 1))[side]
|
||||
|
||||
def new_fixed_axis(self, loc,
|
||||
nth_coord=None,
|
||||
axis_direction=None,
|
||||
offset=None,
|
||||
axes=None):
|
||||
if axes is None:
|
||||
axes = self.axes
|
||||
if axis_direction is None:
|
||||
axis_direction = loc
|
||||
# This is not the same as the FixedAxisArtistHelper class used by
|
||||
# grid_helper_curvelinear.GridHelperCurveLinear.new_fixed_axis!
|
||||
_helper = FixedAxisArtistHelper(
|
||||
self, loc, nth_coord_ticks=nth_coord)
|
||||
axisline = AxisArtist(axes, _helper, axis_direction=axis_direction)
|
||||
# Perhaps should be moved to the base class?
|
||||
axisline.line.set_clip_on(True)
|
||||
axisline.line.set_clip_box(axisline.axes.bbox)
|
||||
return axisline
|
||||
|
||||
# new_floating_axis will inherit the grid_helper's extremes.
|
||||
|
||||
# def new_floating_axis(self, nth_coord,
|
||||
# value,
|
||||
# axes=None,
|
||||
# axis_direction="bottom"
|
||||
# ):
|
||||
|
||||
# axis = super(GridHelperCurveLinear,
|
||||
# self).new_floating_axis(nth_coord,
|
||||
# value, axes=axes,
|
||||
# axis_direction=axis_direction)
|
||||
|
||||
# # set extreme values of the axis helper
|
||||
# if nth_coord == 1:
|
||||
# axis.get_helper().set_extremes(*self._extremes[:2])
|
||||
# elif nth_coord == 0:
|
||||
# axis.get_helper().set_extremes(*self._extremes[2:])
|
||||
|
||||
# return axis
|
||||
|
||||
def _update_grid(self, x1, y1, x2, y2):
|
||||
if self.grid_info is None:
|
||||
self.grid_info = dict()
|
||||
|
||||
grid_info = self.grid_info
|
||||
|
||||
grid_finder = self.grid_finder
|
||||
extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy,
|
||||
x1, y1, x2, y2)
|
||||
|
||||
lon_min, lon_max = sorted(extremes[:2])
|
||||
lat_min, lat_max = sorted(extremes[2:])
|
||||
lon_levs, lon_n, lon_factor = \
|
||||
grid_finder.grid_locator1(lon_min, lon_max)
|
||||
lat_levs, lat_n, lat_factor = \
|
||||
grid_finder.grid_locator2(lat_min, lat_max)
|
||||
grid_info["extremes"] = lon_min, lon_max, lat_min, lat_max # extremes
|
||||
|
||||
grid_info["lon_info"] = lon_levs, lon_n, lon_factor
|
||||
grid_info["lat_info"] = lat_levs, lat_n, lat_factor
|
||||
|
||||
grid_info["lon_labels"] = grid_finder.tick_formatter1("bottom",
|
||||
lon_factor,
|
||||
lon_levs)
|
||||
|
||||
grid_info["lat_labels"] = grid_finder.tick_formatter2("bottom",
|
||||
lat_factor,
|
||||
lat_levs)
|
||||
|
||||
if lon_factor is None:
|
||||
lon_values = np.asarray(lon_levs[:lon_n])
|
||||
else:
|
||||
lon_values = np.asarray(lon_levs[:lon_n]/lon_factor)
|
||||
if lat_factor is None:
|
||||
lat_values = np.asarray(lat_levs[:lat_n])
|
||||
else:
|
||||
lat_values = np.asarray(lat_levs[:lat_n]/lat_factor)
|
||||
|
||||
lon_lines, lat_lines = grid_finder._get_raw_grid_lines(
|
||||
lon_values[(lon_min < lon_values) & (lon_values < lon_max)],
|
||||
lat_values[(lat_min < lat_values) & (lat_values < lat_max)],
|
||||
lon_min, lon_max, lat_min, lat_max)
|
||||
|
||||
grid_info["lon_lines"] = lon_lines
|
||||
grid_info["lat_lines"] = lat_lines
|
||||
|
||||
lon_lines, lat_lines = grid_finder._get_raw_grid_lines(
|
||||
# lon_min, lon_max, lat_min, lat_max)
|
||||
extremes[:2], extremes[2:], *extremes)
|
||||
|
||||
grid_info["lon_lines0"] = lon_lines
|
||||
grid_info["lat_lines0"] = lat_lines
|
||||
|
||||
def get_gridlines(self, which="major", axis="both"):
|
||||
grid_lines = []
|
||||
if axis in ["both", "x"]:
|
||||
grid_lines.extend(self.grid_info["lon_lines"])
|
||||
if axis in ["both", "y"]:
|
||||
grid_lines.extend(self.grid_info["lat_lines"])
|
||||
return grid_lines
|
||||
|
||||
def get_boundary(self):
|
||||
"""
|
||||
Return (N, 2) array of (x, y) coordinate of the boundary.
|
||||
"""
|
||||
x0, x1, y0, y1 = self._extremes
|
||||
tr = self._aux_trans
|
||||
|
||||
xx = np.linspace(x0, x1, 100)
|
||||
yy0 = np.full_like(xx, y0)
|
||||
yy1 = np.full_like(xx, y1)
|
||||
yy = np.linspace(y0, y1, 100)
|
||||
xx0 = np.full_like(yy, x0)
|
||||
xx1 = np.full_like(yy, x1)
|
||||
|
||||
xxx = np.concatenate([xx[:-1], xx1[:-1], xx[-1:0:-1], xx0])
|
||||
yyy = np.concatenate([yy0[:-1], yy[:-1], yy1[:-1], yy[::-1]])
|
||||
t = tr.transform(np.array([xxx, yyy]).transpose())
|
||||
|
||||
return t
|
||||
|
||||
|
||||
class FloatingAxesBase:
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
grid_helper = kwargs.get("grid_helper", None)
|
||||
if grid_helper is None:
|
||||
raise ValueError("FloatingAxes requires grid_helper argument")
|
||||
if not hasattr(grid_helper, "get_boundary"):
|
||||
raise ValueError("grid_helper must implement get_boundary method")
|
||||
|
||||
self._axes_class_floating.__init__(self, *args, **kwargs)
|
||||
|
||||
self.set_aspect(1.)
|
||||
self.adjust_axes_lim()
|
||||
|
||||
def _gen_axes_patch(self):
|
||||
# docstring inherited
|
||||
grid_helper = self.get_grid_helper()
|
||||
t = grid_helper.get_boundary()
|
||||
return mpatches.Polygon(t)
|
||||
|
||||
def cla(self):
|
||||
self._axes_class_floating.cla(self)
|
||||
# HostAxes.cla(self)
|
||||
self.patch.set_transform(self.transData)
|
||||
|
||||
patch = self._axes_class_floating._gen_axes_patch(self)
|
||||
patch.set_figure(self.figure)
|
||||
patch.set_visible(False)
|
||||
patch.set_transform(self.transAxes)
|
||||
|
||||
self.patch.set_clip_path(patch)
|
||||
self.gridlines.set_clip_path(patch)
|
||||
|
||||
self._original_patch = patch
|
||||
|
||||
def adjust_axes_lim(self):
|
||||
grid_helper = self.get_grid_helper()
|
||||
t = grid_helper.get_boundary()
|
||||
x, y = t[:, 0], t[:, 1]
|
||||
|
||||
xmin, xmax = min(x), max(x)
|
||||
ymin, ymax = min(y), max(y)
|
||||
|
||||
dx = (xmax-xmin) / 100
|
||||
dy = (ymax-ymin) / 100
|
||||
|
||||
self.set_xlim(xmin-dx, xmax+dx)
|
||||
self.set_ylim(ymin-dy, ymax+dy)
|
||||
|
||||
|
||||
@functools.lru_cache(None)
|
||||
def floatingaxes_class_factory(axes_class):
|
||||
return type("Floating %s" % axes_class.__name__,
|
||||
(FloatingAxesBase, axes_class),
|
||||
{'_axes_class_floating': axes_class})
|
||||
|
||||
|
||||
FloatingAxes = floatingaxes_class_factory(
|
||||
host_axes_class_factory(axislines.Axes))
|
||||
FloatingSubplot = maxes.subplot_class_factory(FloatingAxes)
|
304
venv/Lib/site-packages/mpl_toolkits/axisartist/grid_finder.py
Normal file
304
venv/Lib/site-packages/mpl_toolkits/axisartist/grid_finder.py
Normal file
|
@ -0,0 +1,304 @@
|
|||
import numpy as np
|
||||
|
||||
from matplotlib import cbook, ticker as mticker
|
||||
from matplotlib.transforms import Bbox, Transform
|
||||
from .clip_path import clip_line_to_rect
|
||||
|
||||
|
||||
def _deprecate_factor_none(factor):
|
||||
# After the deprecation period, calls to _deprecate_factor_none can just be
|
||||
# removed.
|
||||
if factor is None:
|
||||
cbook.warn_deprecated(
|
||||
"3.2", message="factor=None is deprecated since %(since)s and "
|
||||
"support will be removed %(removal)s; use/return factor=1 instead")
|
||||
factor = 1
|
||||
return factor
|
||||
|
||||
|
||||
class ExtremeFinderSimple:
|
||||
"""
|
||||
A helper class to figure out the range of grid lines that need to be drawn.
|
||||
"""
|
||||
|
||||
def __init__(self, nx, ny):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
nx, ny : int
|
||||
The number of samples in each direction.
|
||||
"""
|
||||
self.nx = nx
|
||||
self.ny = ny
|
||||
|
||||
def __call__(self, transform_xy, x1, y1, x2, y2):
|
||||
"""
|
||||
Compute an approximation of the bounding box obtained by applying
|
||||
*transform_xy* to the box delimited by ``(x1, y1, x2, y2)``.
|
||||
|
||||
The intended use is to have ``(x1, y1, x2, y2)`` in axes coordinates,
|
||||
and have *transform_xy* be the transform from axes coordinates to data
|
||||
coordinates; this method then returns the range of data coordinates
|
||||
that span the actual axes.
|
||||
|
||||
The computation is done by sampling ``nx * ny`` equispaced points in
|
||||
the ``(x1, y1, x2, y2)`` box and finding the resulting points with
|
||||
extremal coordinates; then adding some padding to take into account the
|
||||
finite sampling.
|
||||
|
||||
As each sampling step covers a relative range of *1/nx* or *1/ny*,
|
||||
the padding is computed by expanding the span covered by the extremal
|
||||
coordinates by these fractions.
|
||||
"""
|
||||
x, y = np.meshgrid(
|
||||
np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny))
|
||||
xt, yt = transform_xy(np.ravel(x), np.ravel(y))
|
||||
return self._add_pad(xt.min(), xt.max(), yt.min(), yt.max())
|
||||
|
||||
def _add_pad(self, x_min, x_max, y_min, y_max):
|
||||
"""Perform the padding mentioned in `__call__`."""
|
||||
dx = (x_max - x_min) / self.nx
|
||||
dy = (y_max - y_min) / self.ny
|
||||
return x_min - dx, x_max + dx, y_min - dy, y_max + dy
|
||||
|
||||
|
||||
class GridFinder:
|
||||
def __init__(self,
|
||||
transform,
|
||||
extreme_finder=None,
|
||||
grid_locator1=None,
|
||||
grid_locator2=None,
|
||||
tick_formatter1=None,
|
||||
tick_formatter2=None):
|
||||
"""
|
||||
transform : transform from the image coordinate (which will be
|
||||
the transData of the axes to the world coordinate.
|
||||
|
||||
or transform = (transform_xy, inv_transform_xy)
|
||||
|
||||
locator1, locator2 : grid locator for 1st and 2nd axis.
|
||||
"""
|
||||
if extreme_finder is None:
|
||||
extreme_finder = ExtremeFinderSimple(20, 20)
|
||||
if grid_locator1 is None:
|
||||
grid_locator1 = MaxNLocator()
|
||||
if grid_locator2 is None:
|
||||
grid_locator2 = MaxNLocator()
|
||||
if tick_formatter1 is None:
|
||||
tick_formatter1 = FormatterPrettyPrint()
|
||||
if tick_formatter2 is None:
|
||||
tick_formatter2 = FormatterPrettyPrint()
|
||||
self.extreme_finder = extreme_finder
|
||||
self.grid_locator1 = grid_locator1
|
||||
self.grid_locator2 = grid_locator2
|
||||
self.tick_formatter1 = tick_formatter1
|
||||
self.tick_formatter2 = tick_formatter2
|
||||
self.update_transform(transform)
|
||||
|
||||
def get_grid_info(self, x1, y1, x2, y2):
|
||||
"""
|
||||
lon_values, lat_values : list of grid values. if integer is given,
|
||||
rough number of grids in each direction.
|
||||
"""
|
||||
|
||||
extremes = self.extreme_finder(self.inv_transform_xy, x1, y1, x2, y2)
|
||||
|
||||
# min & max rage of lat (or lon) for each grid line will be drawn.
|
||||
# i.e., gridline of lon=0 will be drawn from lat_min to lat_max.
|
||||
|
||||
lon_min, lon_max, lat_min, lat_max = extremes
|
||||
lon_levs, lon_n, lon_factor = self.grid_locator1(lon_min, lon_max)
|
||||
lat_levs, lat_n, lat_factor = self.grid_locator2(lat_min, lat_max)
|
||||
|
||||
lon_values = lon_levs[:lon_n] / _deprecate_factor_none(lon_factor)
|
||||
lat_values = lat_levs[:lat_n] / _deprecate_factor_none(lat_factor)
|
||||
|
||||
lon_lines, lat_lines = self._get_raw_grid_lines(lon_values,
|
||||
lat_values,
|
||||
lon_min, lon_max,
|
||||
lat_min, lat_max)
|
||||
|
||||
ddx = (x2-x1)*1.e-10
|
||||
ddy = (y2-y1)*1.e-10
|
||||
bb = Bbox.from_extents(x1-ddx, y1-ddy, x2+ddx, y2+ddy)
|
||||
|
||||
grid_info = {
|
||||
"extremes": extremes,
|
||||
"lon_lines": lon_lines,
|
||||
"lat_lines": lat_lines,
|
||||
"lon": self._clip_grid_lines_and_find_ticks(
|
||||
lon_lines, lon_values, lon_levs, bb),
|
||||
"lat": self._clip_grid_lines_and_find_ticks(
|
||||
lat_lines, lat_values, lat_levs, bb),
|
||||
}
|
||||
|
||||
tck_labels = grid_info["lon"]["tick_labels"] = {}
|
||||
for direction in ["left", "bottom", "right", "top"]:
|
||||
levs = grid_info["lon"]["tick_levels"][direction]
|
||||
tck_labels[direction] = self.tick_formatter1(
|
||||
direction, lon_factor, levs)
|
||||
|
||||
tck_labels = grid_info["lat"]["tick_labels"] = {}
|
||||
for direction in ["left", "bottom", "right", "top"]:
|
||||
levs = grid_info["lat"]["tick_levels"][direction]
|
||||
tck_labels[direction] = self.tick_formatter2(
|
||||
direction, lat_factor, levs)
|
||||
|
||||
return grid_info
|
||||
|
||||
def _get_raw_grid_lines(self,
|
||||
lon_values, lat_values,
|
||||
lon_min, lon_max, lat_min, lat_max):
|
||||
|
||||
lons_i = np.linspace(lon_min, lon_max, 100) # for interpolation
|
||||
lats_i = np.linspace(lat_min, lat_max, 100)
|
||||
|
||||
lon_lines = [self.transform_xy(np.full_like(lats_i, lon), lats_i)
|
||||
for lon in lon_values]
|
||||
lat_lines = [self.transform_xy(lons_i, np.full_like(lons_i, lat))
|
||||
for lat in lat_values]
|
||||
|
||||
return lon_lines, lat_lines
|
||||
|
||||
def _clip_grid_lines_and_find_ticks(self, lines, values, levs, bb):
|
||||
gi = {
|
||||
"values": [],
|
||||
"levels": [],
|
||||
"tick_levels": dict(left=[], bottom=[], right=[], top=[]),
|
||||
"tick_locs": dict(left=[], bottom=[], right=[], top=[]),
|
||||
"lines": [],
|
||||
}
|
||||
|
||||
tck_levels = gi["tick_levels"]
|
||||
tck_locs = gi["tick_locs"]
|
||||
for (lx, ly), v, lev in zip(lines, values, levs):
|
||||
xy, tcks = clip_line_to_rect(lx, ly, bb)
|
||||
if not xy:
|
||||
continue
|
||||
gi["levels"].append(v)
|
||||
gi["lines"].append(xy)
|
||||
|
||||
for tck, direction in zip(tcks,
|
||||
["left", "bottom", "right", "top"]):
|
||||
for t in tck:
|
||||
tck_levels[direction].append(lev)
|
||||
tck_locs[direction].append(t)
|
||||
|
||||
return gi
|
||||
|
||||
def update_transform(self, aux_trans):
|
||||
if isinstance(aux_trans, Transform):
|
||||
def transform_xy(x, y):
|
||||
ll1 = np.column_stack([x, y])
|
||||
ll2 = aux_trans.transform(ll1)
|
||||
lon, lat = ll2[:, 0], ll2[:, 1]
|
||||
return lon, lat
|
||||
|
||||
def inv_transform_xy(x, y):
|
||||
ll1 = np.column_stack([x, y])
|
||||
ll2 = aux_trans.inverted().transform(ll1)
|
||||
lon, lat = ll2[:, 0], ll2[:, 1]
|
||||
return lon, lat
|
||||
|
||||
else:
|
||||
transform_xy, inv_transform_xy = aux_trans
|
||||
|
||||
self.transform_xy = transform_xy
|
||||
self.inv_transform_xy = inv_transform_xy
|
||||
|
||||
def update(self, **kw):
|
||||
for k in kw:
|
||||
if k in ["extreme_finder",
|
||||
"grid_locator1",
|
||||
"grid_locator2",
|
||||
"tick_formatter1",
|
||||
"tick_formatter2"]:
|
||||
setattr(self, k, kw[k])
|
||||
else:
|
||||
raise ValueError("Unknown update property '%s'" % k)
|
||||
|
||||
|
||||
@cbook.deprecated("3.2")
|
||||
class GridFinderBase(GridFinder):
|
||||
def __init__(self,
|
||||
extreme_finder,
|
||||
grid_locator1=None,
|
||||
grid_locator2=None,
|
||||
tick_formatter1=None,
|
||||
tick_formatter2=None):
|
||||
super().__init__((None, None), extreme_finder,
|
||||
grid_locator1, grid_locator2,
|
||||
tick_formatter1, tick_formatter2)
|
||||
|
||||
|
||||
class MaxNLocator(mticker.MaxNLocator):
|
||||
def __init__(self, nbins=10, steps=None,
|
||||
trim=True,
|
||||
integer=False,
|
||||
symmetric=False,
|
||||
prune=None):
|
||||
# trim argument has no effect. It has been left for API compatibility
|
||||
mticker.MaxNLocator.__init__(self, nbins, steps=steps,
|
||||
integer=integer,
|
||||
symmetric=symmetric, prune=prune)
|
||||
self.create_dummy_axis()
|
||||
self._factor = 1
|
||||
|
||||
def __call__(self, v1, v2):
|
||||
self.set_bounds(v1 * self._factor, v2 * self._factor)
|
||||
locs = mticker.MaxNLocator.__call__(self)
|
||||
return np.array(locs), len(locs), self._factor
|
||||
|
||||
@cbook.deprecated("3.3")
|
||||
def set_factor(self, f):
|
||||
self._factor = _deprecate_factor_none(f)
|
||||
|
||||
|
||||
class FixedLocator:
|
||||
def __init__(self, locs):
|
||||
self._locs = locs
|
||||
self._factor = 1
|
||||
|
||||
def __call__(self, v1, v2):
|
||||
v1, v2 = sorted([v1 * self._factor, v2 * self._factor])
|
||||
locs = np.array([l for l in self._locs if v1 <= l <= v2])
|
||||
return locs, len(locs), self._factor
|
||||
|
||||
@cbook.deprecated("3.3")
|
||||
def set_factor(self, f):
|
||||
self._factor = _deprecate_factor_none(f)
|
||||
|
||||
|
||||
# Tick Formatter
|
||||
|
||||
class FormatterPrettyPrint:
|
||||
def __init__(self, useMathText=True):
|
||||
self._fmt = mticker.ScalarFormatter(
|
||||
useMathText=useMathText, useOffset=False)
|
||||
self._fmt.create_dummy_axis()
|
||||
|
||||
def __call__(self, direction, factor, values):
|
||||
return self._fmt.format_ticks(values)
|
||||
|
||||
|
||||
class DictFormatter:
|
||||
def __init__(self, format_dict, formatter=None):
|
||||
"""
|
||||
format_dict : dictionary for format strings to be used.
|
||||
formatter : fall-back formatter
|
||||
"""
|
||||
super().__init__()
|
||||
self._format_dict = format_dict
|
||||
self._fallback_formatter = formatter
|
||||
|
||||
def __call__(self, direction, factor, values):
|
||||
"""
|
||||
factor is ignored if value is found in the dictionary
|
||||
"""
|
||||
if self._fallback_formatter:
|
||||
fallback_strings = self._fallback_formatter(
|
||||
direction, factor, values)
|
||||
else:
|
||||
fallback_strings = [""] * len(values)
|
||||
return [self._format_dict.get(k, v)
|
||||
for k, v in zip(values, fallback_strings)]
|
|
@ -0,0 +1,398 @@
|
|||
"""
|
||||
An experimental support for curvilinear grid.
|
||||
"""
|
||||
from itertools import chain
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib.path import Path
|
||||
from matplotlib.transforms import Affine2D, IdentityTransform
|
||||
from .axislines import AxisArtistHelper, GridHelperBase
|
||||
from .axis_artist import AxisArtist
|
||||
from .grid_finder import GridFinder, _deprecate_factor_none
|
||||
|
||||
|
||||
class FixedAxisArtistHelper(AxisArtistHelper.Fixed):
|
||||
"""
|
||||
Helper class for a fixed axis.
|
||||
"""
|
||||
|
||||
def __init__(self, grid_helper, side, nth_coord_ticks=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies.
|
||||
nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
|
||||
super().__init__(loc=side)
|
||||
|
||||
self.grid_helper = grid_helper
|
||||
if nth_coord_ticks is None:
|
||||
nth_coord_ticks = self.nth_coord
|
||||
self.nth_coord_ticks = nth_coord_ticks
|
||||
|
||||
self.side = side
|
||||
self._limits_inverted = False
|
||||
|
||||
def update_lim(self, axes):
|
||||
self.grid_helper.update_lim(axes)
|
||||
|
||||
if self.nth_coord == 0:
|
||||
xy1, xy2 = axes.get_ylim()
|
||||
else:
|
||||
xy1, xy2 = axes.get_xlim()
|
||||
|
||||
if xy1 > xy2:
|
||||
self._limits_inverted = True
|
||||
else:
|
||||
self._limits_inverted = False
|
||||
|
||||
def change_tick_coord(self, coord_number=None):
|
||||
if coord_number is None:
|
||||
self.nth_coord_ticks = 1 - self.nth_coord_ticks
|
||||
elif coord_number in [0, 1]:
|
||||
self.nth_coord_ticks = coord_number
|
||||
else:
|
||||
raise Exception("wrong coord number")
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return axes.transData
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label"""
|
||||
|
||||
g = self.grid_helper
|
||||
|
||||
if self._limits_inverted:
|
||||
side = {"left": "right", "right": "left",
|
||||
"top": "bottom", "bottom": "top"}[self.side]
|
||||
else:
|
||||
side = self.side
|
||||
|
||||
ti1 = g.get_tick_iterator(self.nth_coord_ticks, side)
|
||||
ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, side, minor=True)
|
||||
|
||||
return chain(ti1, ti2), iter([])
|
||||
|
||||
|
||||
class FloatingAxisArtistHelper(AxisArtistHelper.Floating):
|
||||
|
||||
def __init__(self, grid_helper, nth_coord, value, axis_direction=None):
|
||||
"""
|
||||
nth_coord = along which coordinate value varies.
|
||||
nth_coord = 0 -> x axis, nth_coord = 1 -> y axis
|
||||
"""
|
||||
|
||||
super().__init__(nth_coord, value)
|
||||
self.value = value
|
||||
self.grid_helper = grid_helper
|
||||
self._extremes = -np.inf, np.inf
|
||||
|
||||
self._get_line_path = None # a method that returns a Path.
|
||||
self._line_num_points = 100 # number of points to create a line
|
||||
|
||||
def set_extremes(self, e1, e2):
|
||||
if e1 is None:
|
||||
e1 = -np.inf
|
||||
if e2 is None:
|
||||
e2 = np.inf
|
||||
self._extremes = e1, e2
|
||||
|
||||
def update_lim(self, axes):
|
||||
self.grid_helper.update_lim(axes)
|
||||
|
||||
x1, x2 = axes.get_xlim()
|
||||
y1, y2 = axes.get_ylim()
|
||||
grid_finder = self.grid_helper.grid_finder
|
||||
extremes = grid_finder.extreme_finder(grid_finder.inv_transform_xy,
|
||||
x1, y1, x2, y2)
|
||||
|
||||
lon_min, lon_max, lat_min, lat_max = extremes
|
||||
e_min, e_max = self._extremes # ranges of other coordinates
|
||||
if self.nth_coord == 0:
|
||||
lat_min = max(e_min, lat_min)
|
||||
lat_max = min(e_max, lat_max)
|
||||
elif self.nth_coord == 1:
|
||||
lon_min = max(e_min, lon_min)
|
||||
lon_max = min(e_max, lon_max)
|
||||
|
||||
lon_levs, lon_n, lon_factor = \
|
||||
grid_finder.grid_locator1(lon_min, lon_max)
|
||||
lat_levs, lat_n, lat_factor = \
|
||||
grid_finder.grid_locator2(lat_min, lat_max)
|
||||
|
||||
if self.nth_coord == 0:
|
||||
xx0 = np.full(self._line_num_points, self.value, type(self.value))
|
||||
yy0 = np.linspace(lat_min, lat_max, self._line_num_points)
|
||||
xx, yy = grid_finder.transform_xy(xx0, yy0)
|
||||
elif self.nth_coord == 1:
|
||||
xx0 = np.linspace(lon_min, lon_max, self._line_num_points)
|
||||
yy0 = np.full(self._line_num_points, self.value, type(self.value))
|
||||
xx, yy = grid_finder.transform_xy(xx0, yy0)
|
||||
|
||||
self.grid_info = {
|
||||
"extremes": (lon_min, lon_max, lat_min, lat_max),
|
||||
"lon_info": (lon_levs, lon_n, _deprecate_factor_none(lon_factor)),
|
||||
"lat_info": (lat_levs, lat_n, _deprecate_factor_none(lat_factor)),
|
||||
"lon_labels": grid_finder.tick_formatter1(
|
||||
"bottom", _deprecate_factor_none(lon_factor), lon_levs),
|
||||
"lat_labels": grid_finder.tick_formatter2(
|
||||
"bottom", _deprecate_factor_none(lat_factor), lat_levs),
|
||||
"line_xy": (xx, yy),
|
||||
}
|
||||
|
||||
def get_axislabel_transform(self, axes):
|
||||
return Affine2D() # axes.transData
|
||||
|
||||
def get_axislabel_pos_angle(self, axes):
|
||||
|
||||
extremes = self.grid_info["extremes"]
|
||||
|
||||
if self.nth_coord == 0:
|
||||
xx0 = self.value
|
||||
yy0 = (extremes[2] + extremes[3]) / 2
|
||||
dxx = 0
|
||||
dyy = abs(extremes[2] - extremes[3]) / 1000
|
||||
elif self.nth_coord == 1:
|
||||
xx0 = (extremes[0] + extremes[1]) / 2
|
||||
yy0 = self.value
|
||||
dxx = abs(extremes[0] - extremes[1]) / 1000
|
||||
dyy = 0
|
||||
|
||||
grid_finder = self.grid_helper.grid_finder
|
||||
(xx1,), (yy1,) = grid_finder.transform_xy([xx0], [yy0])
|
||||
|
||||
data_to_axes = axes.transData - axes.transAxes
|
||||
p = data_to_axes.transform([xx1, yy1])
|
||||
|
||||
if 0 <= p[0] <= 1 and 0 <= p[1] <= 1:
|
||||
xx1c, yy1c = axes.transData.transform([xx1, yy1])
|
||||
(xx2,), (yy2,) = grid_finder.transform_xy([xx0 + dxx], [yy0 + dyy])
|
||||
xx2c, yy2c = axes.transData.transform([xx2, yy2])
|
||||
return (xx1c, yy1c), np.rad2deg(np.arctan2(yy2c-yy1c, xx2c-xx1c))
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def get_tick_transform(self, axes):
|
||||
return IdentityTransform() # axes.transData
|
||||
|
||||
def get_tick_iterators(self, axes):
|
||||
"""tick_loc, tick_angle, tick_label, (optionally) tick_label"""
|
||||
|
||||
grid_finder = self.grid_helper.grid_finder
|
||||
|
||||
lat_levs, lat_n, lat_factor = self.grid_info["lat_info"]
|
||||
lat_levs = np.asarray(lat_levs)
|
||||
yy0 = lat_levs / _deprecate_factor_none(lat_factor)
|
||||
dy = 0.01 / _deprecate_factor_none(lat_factor)
|
||||
|
||||
lon_levs, lon_n, lon_factor = self.grid_info["lon_info"]
|
||||
lon_levs = np.asarray(lon_levs)
|
||||
xx0 = lon_levs / _deprecate_factor_none(lon_factor)
|
||||
dx = 0.01 / _deprecate_factor_none(lon_factor)
|
||||
|
||||
if None in self._extremes:
|
||||
e0, e1 = self._extremes
|
||||
else:
|
||||
e0, e1 = sorted(self._extremes)
|
||||
if e0 is None:
|
||||
e0 = -np.inf
|
||||
if e1 is None:
|
||||
e1 = np.inf
|
||||
|
||||
if self.nth_coord == 0:
|
||||
mask = (e0 <= yy0) & (yy0 <= e1)
|
||||
#xx0, yy0 = xx0[mask], yy0[mask]
|
||||
yy0 = yy0[mask]
|
||||
elif self.nth_coord == 1:
|
||||
mask = (e0 <= xx0) & (xx0 <= e1)
|
||||
#xx0, yy0 = xx0[mask], yy0[mask]
|
||||
xx0 = xx0[mask]
|
||||
|
||||
def transform_xy(x, y):
|
||||
x1, y1 = grid_finder.transform_xy(x, y)
|
||||
x2y2 = axes.transData.transform(np.array([x1, y1]).transpose())
|
||||
x2, y2 = x2y2.transpose()
|
||||
return x2, y2
|
||||
|
||||
# find angles
|
||||
if self.nth_coord == 0:
|
||||
xx0 = np.full_like(yy0, self.value)
|
||||
|
||||
xx1, yy1 = transform_xy(xx0, yy0)
|
||||
|
||||
xx00 = xx0.copy()
|
||||
xx00[xx0 + dx > e1] -= dx
|
||||
xx1a, yy1a = transform_xy(xx00, yy0)
|
||||
xx1b, yy1b = transform_xy(xx00+dx, yy0)
|
||||
|
||||
xx2a, yy2a = transform_xy(xx0, yy0)
|
||||
xx2b, yy2b = transform_xy(xx0, yy0+dy)
|
||||
|
||||
labels = self.grid_info["lat_labels"]
|
||||
labels = [l for l, m in zip(labels, mask) if m]
|
||||
|
||||
elif self.nth_coord == 1:
|
||||
yy0 = np.full_like(xx0, self.value)
|
||||
|
||||
xx1, yy1 = transform_xy(xx0, yy0)
|
||||
|
||||
xx1a, yy1a = transform_xy(xx0, yy0)
|
||||
xx1b, yy1b = transform_xy(xx0, yy0+dy)
|
||||
|
||||
xx00 = xx0.copy()
|
||||
xx00[xx0 + dx > e1] -= dx
|
||||
xx2a, yy2a = transform_xy(xx00, yy0)
|
||||
xx2b, yy2b = transform_xy(xx00+dx, yy0)
|
||||
|
||||
labels = self.grid_info["lon_labels"]
|
||||
labels = [l for l, m in zip(labels, mask) if m]
|
||||
|
||||
def f1():
|
||||
dd = np.arctan2(yy1b-yy1a, xx1b-xx1a) # angle normal
|
||||
dd2 = np.arctan2(yy2b-yy2a, xx2b-xx2a) # angle tangent
|
||||
mm = (yy1b == yy1a) & (xx1b == xx1a) # mask where dd not defined
|
||||
dd[mm] = dd2[mm] + np.pi / 2
|
||||
|
||||
tick_to_axes = self.get_tick_transform(axes) - axes.transAxes
|
||||
for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels):
|
||||
c2 = tick_to_axes.transform((x, y))
|
||||
delta = 0.00001
|
||||
if 0-delta <= c2[0] <= 1+delta and 0-delta <= c2[1] <= 1+delta:
|
||||
d1, d2 = np.rad2deg([d, d2])
|
||||
yield [x, y], d1, d2, lab
|
||||
|
||||
return f1(), iter([])
|
||||
|
||||
def get_line_transform(self, axes):
|
||||
return axes.transData
|
||||
|
||||
def get_line(self, axes):
|
||||
self.update_lim(axes)
|
||||
x, y = self.grid_info["line_xy"]
|
||||
|
||||
if self._get_line_path is None:
|
||||
return Path(np.column_stack([x, y]))
|
||||
else:
|
||||
return self._get_line_path(axes, x, y)
|
||||
|
||||
|
||||
class GridHelperCurveLinear(GridHelperBase):
|
||||
|
||||
def __init__(self, aux_trans,
|
||||
extreme_finder=None,
|
||||
grid_locator1=None,
|
||||
grid_locator2=None,
|
||||
tick_formatter1=None,
|
||||
tick_formatter2=None):
|
||||
"""
|
||||
aux_trans : a transform from the source (curved) coordinate to
|
||||
target (rectilinear) coordinate. An instance of MPL's Transform
|
||||
(inverse transform should be defined) or a tuple of two callable
|
||||
objects which defines the transform and its inverse. The callables
|
||||
need take two arguments of array of source coordinates and
|
||||
should return two target coordinates.
|
||||
|
||||
e.g., ``x2, y2 = trans(x1, y1)``
|
||||
"""
|
||||
super().__init__()
|
||||
self.grid_info = None
|
||||
self._old_values = None
|
||||
self._aux_trans = aux_trans
|
||||
self.grid_finder = GridFinder(aux_trans,
|
||||
extreme_finder,
|
||||
grid_locator1,
|
||||
grid_locator2,
|
||||
tick_formatter1,
|
||||
tick_formatter2)
|
||||
|
||||
def update_grid_finder(self, aux_trans=None, **kw):
|
||||
if aux_trans is not None:
|
||||
self.grid_finder.update_transform(aux_trans)
|
||||
self.grid_finder.update(**kw)
|
||||
self.invalidate()
|
||||
|
||||
def _update(self, x1, x2, y1, y2):
|
||||
"""bbox in 0-based image coordinates"""
|
||||
# update wcsgrid
|
||||
if self.valid() and self._old_values == (x1, x2, y1, y2):
|
||||
return
|
||||
self._update_grid(x1, y1, x2, y2)
|
||||
self._old_values = (x1, x2, y1, y2)
|
||||
self._force_update = False
|
||||
|
||||
def new_fixed_axis(self, loc,
|
||||
nth_coord=None,
|
||||
axis_direction=None,
|
||||
offset=None,
|
||||
axes=None):
|
||||
if axes is None:
|
||||
axes = self.axes
|
||||
if axis_direction is None:
|
||||
axis_direction = loc
|
||||
_helper = FixedAxisArtistHelper(self, loc, nth_coord_ticks=nth_coord)
|
||||
axisline = AxisArtist(axes, _helper, axis_direction=axis_direction)
|
||||
# Why is clip not set on axisline, unlike in new_floating_axis or in
|
||||
# the floating_axig.GridHelperCurveLinear subclass?
|
||||
return axisline
|
||||
|
||||
def new_floating_axis(self, nth_coord,
|
||||
value,
|
||||
axes=None,
|
||||
axis_direction="bottom"
|
||||
):
|
||||
|
||||
if axes is None:
|
||||
axes = self.axes
|
||||
|
||||
_helper = FloatingAxisArtistHelper(
|
||||
self, nth_coord, value, axis_direction)
|
||||
|
||||
axisline = AxisArtist(axes, _helper)
|
||||
|
||||
# _helper = FloatingAxisArtistHelper(self, nth_coord,
|
||||
# value,
|
||||
# label_direction=label_direction,
|
||||
# )
|
||||
|
||||
# axisline = AxisArtistFloating(axes, _helper,
|
||||
# axis_direction=axis_direction)
|
||||
axisline.line.set_clip_on(True)
|
||||
axisline.line.set_clip_box(axisline.axes.bbox)
|
||||
# axisline.major_ticklabels.set_visible(True)
|
||||
# axisline.minor_ticklabels.set_visible(False)
|
||||
|
||||
return axisline
|
||||
|
||||
def _update_grid(self, x1, y1, x2, y2):
|
||||
self.grid_info = self.grid_finder.get_grid_info(x1, y1, x2, y2)
|
||||
|
||||
def get_gridlines(self, which="major", axis="both"):
|
||||
grid_lines = []
|
||||
if axis in ["both", "x"]:
|
||||
for gl in self.grid_info["lon"]["lines"]:
|
||||
grid_lines.extend(gl)
|
||||
if axis in ["both", "y"]:
|
||||
for gl in self.grid_info["lat"]["lines"]:
|
||||
grid_lines.extend(gl)
|
||||
return grid_lines
|
||||
|
||||
def get_tick_iterator(self, nth_coord, axis_side, minor=False):
|
||||
|
||||
# axisnr = dict(left=0, bottom=1, right=2, top=3)[axis_side]
|
||||
angle_tangent = dict(left=90, right=90, bottom=0, top=0)[axis_side]
|
||||
# angle = [0, 90, 180, 270][axisnr]
|
||||
lon_or_lat = ["lon", "lat"][nth_coord]
|
||||
if not minor: # major ticks
|
||||
for (xy, a), l in zip(
|
||||
self.grid_info[lon_or_lat]["tick_locs"][axis_side],
|
||||
self.grid_info[lon_or_lat]["tick_labels"][axis_side]):
|
||||
angle_normal = a
|
||||
yield xy, angle_normal, angle_tangent, l
|
||||
else:
|
||||
for (xy, a), l in zip(
|
||||
self.grid_info[lon_or_lat]["tick_locs"][axis_side],
|
||||
self.grid_info[lon_or_lat]["tick_labels"][axis_side]):
|
||||
angle_normal = a
|
||||
yield xy, angle_normal, angle_tangent, ""
|
||||
# for xy, a, l in self.grid_info[lon_or_lat]["ticks"][axis_side]:
|
||||
# yield xy, a, ""
|
|
@ -0,0 +1,10 @@
|
|||
from mpl_toolkits.axes_grid1.parasite_axes import (
|
||||
host_axes_class_factory, parasite_axes_class_factory,
|
||||
parasite_axes_auxtrans_class_factory, subplot_class_factory)
|
||||
from .axislines import Axes
|
||||
|
||||
|
||||
ParasiteAxes = parasite_axes_class_factory(Axes)
|
||||
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(ParasiteAxes)
|
||||
HostAxes = host_axes_class_factory(Axes)
|
||||
SubplotHost = subplot_class_factory(HostAxes)
|
1
venv/Lib/site-packages/mpl_toolkits/mplot3d/__init__.py
Normal file
1
venv/Lib/site-packages/mpl_toolkits/mplot3d/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .axes3d import Axes3D
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
811
venv/Lib/site-packages/mpl_toolkits/mplot3d/art3d.py
Normal file
811
venv/Lib/site-packages/mpl_toolkits/mplot3d/art3d.py
Normal file
|
@ -0,0 +1,811 @@
|
|||
# art3d.py, original mplot3d version by John Porter
|
||||
# Parts rewritten by Reinier Heeres <reinier@heeres.eu>
|
||||
# Minor additions by Ben Axelrod <baxelrod@coroware.com>
|
||||
|
||||
"""
|
||||
Module containing 3D artist code and functions to convert 2D
|
||||
artists into 3D versions which can be added to an Axes3D.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
|
||||
from matplotlib import (
|
||||
artist, colors as mcolors, lines, text as mtext, path as mpath)
|
||||
from matplotlib.collections import (
|
||||
LineCollection, PolyCollection, PatchCollection, PathCollection)
|
||||
from matplotlib.colors import Normalize
|
||||
from matplotlib.patches import Patch
|
||||
from . import proj3d
|
||||
|
||||
|
||||
def _norm_angle(a):
|
||||
"""Return the given angle normalized to -180 < *a* <= 180 degrees."""
|
||||
a = (a + 360) % 360
|
||||
if a > 180:
|
||||
a = a - 360
|
||||
return a
|
||||
|
||||
|
||||
def _norm_text_angle(a):
|
||||
"""Return the given angle normalized to -90 < *a* <= 90 degrees."""
|
||||
a = (a + 180) % 180
|
||||
if a > 90:
|
||||
a = a - 180
|
||||
return a
|
||||
|
||||
|
||||
def get_dir_vector(zdir):
|
||||
"""
|
||||
Return a direction vector.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
zdir : {'x', 'y', 'z', None, 3-tuple}
|
||||
The direction. Possible values are:
|
||||
- 'x': equivalent to (1, 0, 0)
|
||||
- 'y': equivalent to (0, 1, 0)
|
||||
- 'z': equivalent to (0, 0, 1)
|
||||
- *None*: equivalent to (0, 0, 0)
|
||||
- an iterable (x, y, z) is returned unchanged.
|
||||
|
||||
Returns
|
||||
-------
|
||||
x, y, z : array-like
|
||||
The direction vector. This is either a numpy.array or *zdir* itself if
|
||||
*zdir* is already a length-3 iterable.
|
||||
|
||||
"""
|
||||
if zdir == 'x':
|
||||
return np.array((1, 0, 0))
|
||||
elif zdir == 'y':
|
||||
return np.array((0, 1, 0))
|
||||
elif zdir == 'z':
|
||||
return np.array((0, 0, 1))
|
||||
elif zdir is None:
|
||||
return np.array((0, 0, 0))
|
||||
elif np.iterable(zdir) and len(zdir) == 3:
|
||||
return zdir
|
||||
else:
|
||||
raise ValueError("'x', 'y', 'z', None or vector of length 3 expected")
|
||||
|
||||
|
||||
class Text3D(mtext.Text):
|
||||
"""
|
||||
Text object with 3D position and direction.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x, y, z
|
||||
The position of the text.
|
||||
text : str
|
||||
The text string to display.
|
||||
zdir : {'x', 'y', 'z', None, 3-tuple}
|
||||
The direction of the text. See `.get_dir_vector` for a description of
|
||||
the values.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
**kwargs
|
||||
All other parameters are passed on to `~matplotlib.text.Text`.
|
||||
"""
|
||||
|
||||
def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs):
|
||||
mtext.Text.__init__(self, x, y, text, **kwargs)
|
||||
self.set_3d_properties(z, zdir)
|
||||
|
||||
def set_3d_properties(self, z=0, zdir='z'):
|
||||
x, y = self.get_position()
|
||||
self._position3d = np.array((x, y, z))
|
||||
self._dir_vec = get_dir_vector(zdir)
|
||||
self.stale = True
|
||||
|
||||
@artist.allow_rasterization
|
||||
def draw(self, renderer):
|
||||
proj = proj3d.proj_trans_points(
|
||||
[self._position3d, self._position3d + self._dir_vec], renderer.M)
|
||||
dx = proj[0][1] - proj[0][0]
|
||||
dy = proj[1][1] - proj[1][0]
|
||||
angle = math.degrees(math.atan2(dy, dx))
|
||||
self.set_position((proj[0][0], proj[1][0]))
|
||||
self.set_rotation(_norm_text_angle(angle))
|
||||
mtext.Text.draw(self, renderer)
|
||||
self.stale = False
|
||||
|
||||
def get_tightbbox(self, renderer):
|
||||
# Overwriting the 2d Text behavior which is not valid for 3d.
|
||||
# For now, just return None to exclude from layout calculation.
|
||||
return None
|
||||
|
||||
|
||||
def text_2d_to_3d(obj, z=0, zdir='z'):
|
||||
"""Convert a Text to a Text3D object."""
|
||||
obj.__class__ = Text3D
|
||||
obj.set_3d_properties(z, zdir)
|
||||
|
||||
|
||||
class Line3D(lines.Line2D):
|
||||
"""
|
||||
3D line object.
|
||||
"""
|
||||
|
||||
def __init__(self, xs, ys, zs, *args, **kwargs):
|
||||
"""
|
||||
Keyword arguments are passed onto :func:`~matplotlib.lines.Line2D`.
|
||||
"""
|
||||
lines.Line2D.__init__(self, [], [], *args, **kwargs)
|
||||
self._verts3d = xs, ys, zs
|
||||
|
||||
def set_3d_properties(self, zs=0, zdir='z'):
|
||||
xs = self.get_xdata()
|
||||
ys = self.get_ydata()
|
||||
zs = np.broadcast_to(zs, xs.shape)
|
||||
self._verts3d = juggle_axes(xs, ys, zs, zdir)
|
||||
self.stale = True
|
||||
|
||||
def set_data_3d(self, *args):
|
||||
"""
|
||||
Set the x, y and z data
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array-like
|
||||
The x-data to be plotted.
|
||||
y : array-like
|
||||
The y-data to be plotted.
|
||||
z : array-like
|
||||
The z-data to be plotted.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Accepts x, y, z arguments or a single array-like (x, y, z)
|
||||
"""
|
||||
if len(args) == 1:
|
||||
self._verts3d = args[0]
|
||||
else:
|
||||
self._verts3d = args
|
||||
self.stale = True
|
||||
|
||||
def get_data_3d(self):
|
||||
"""
|
||||
Get the current data
|
||||
|
||||
Returns
|
||||
-------
|
||||
verts3d : length-3 tuple or array-like
|
||||
The current data as a tuple or array-like.
|
||||
"""
|
||||
return self._verts3d
|
||||
|
||||
@artist.allow_rasterization
|
||||
def draw(self, renderer):
|
||||
xs3d, ys3d, zs3d = self._verts3d
|
||||
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
|
||||
self.set_data(xs, ys)
|
||||
lines.Line2D.draw(self, renderer)
|
||||
self.stale = False
|
||||
|
||||
|
||||
def line_2d_to_3d(line, zs=0, zdir='z'):
|
||||
"""Convert a 2D line to 3D."""
|
||||
|
||||
line.__class__ = Line3D
|
||||
line.set_3d_properties(zs, zdir)
|
||||
|
||||
|
||||
def _path_to_3d_segment(path, zs=0, zdir='z'):
|
||||
"""Convert a path to a 3D segment."""
|
||||
|
||||
zs = np.broadcast_to(zs, len(path))
|
||||
pathsegs = path.iter_segments(simplify=False, curves=False)
|
||||
seg = [(x, y, z) for (((x, y), code), z) in zip(pathsegs, zs)]
|
||||
seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
|
||||
return seg3d
|
||||
|
||||
|
||||
def _paths_to_3d_segments(paths, zs=0, zdir='z'):
|
||||
"""Convert paths from a collection object to 3D segments."""
|
||||
|
||||
zs = np.broadcast_to(zs, len(paths))
|
||||
segs = [_path_to_3d_segment(path, pathz, zdir)
|
||||
for path, pathz in zip(paths, zs)]
|
||||
return segs
|
||||
|
||||
|
||||
def _path_to_3d_segment_with_codes(path, zs=0, zdir='z'):
|
||||
"""Convert a path to a 3D segment with path codes."""
|
||||
|
||||
zs = np.broadcast_to(zs, len(path))
|
||||
pathsegs = path.iter_segments(simplify=False, curves=False)
|
||||
seg_codes = [((x, y, z), code) for ((x, y), code), z in zip(pathsegs, zs)]
|
||||
if seg_codes:
|
||||
seg, codes = zip(*seg_codes)
|
||||
seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
|
||||
else:
|
||||
seg3d = []
|
||||
codes = []
|
||||
return seg3d, list(codes)
|
||||
|
||||
|
||||
def _paths_to_3d_segments_with_codes(paths, zs=0, zdir='z'):
|
||||
"""
|
||||
Convert paths from a collection object to 3D segments with path codes.
|
||||
"""
|
||||
|
||||
zs = np.broadcast_to(zs, len(paths))
|
||||
segments_codes = [_path_to_3d_segment_with_codes(path, pathz, zdir)
|
||||
for path, pathz in zip(paths, zs)]
|
||||
if segments_codes:
|
||||
segments, codes = zip(*segments_codes)
|
||||
else:
|
||||
segments, codes = [], []
|
||||
return list(segments), list(codes)
|
||||
|
||||
|
||||
class Line3DCollection(LineCollection):
|
||||
"""
|
||||
A collection of 3D lines.
|
||||
"""
|
||||
|
||||
def set_sort_zpos(self, val):
|
||||
"""Set the position to use for z-sorting."""
|
||||
self._sort_zpos = val
|
||||
self.stale = True
|
||||
|
||||
def set_segments(self, segments):
|
||||
"""
|
||||
Set 3D segments.
|
||||
"""
|
||||
self._segments3d = segments
|
||||
LineCollection.set_segments(self, [])
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
"""
|
||||
Project the points according to renderer matrix.
|
||||
"""
|
||||
xyslist = [
|
||||
proj3d.proj_trans_points(points, renderer.M) for points in
|
||||
self._segments3d]
|
||||
segments_2d = [np.column_stack([xs, ys]) for xs, ys, zs in xyslist]
|
||||
LineCollection.set_segments(self, segments_2d)
|
||||
|
||||
# FIXME
|
||||
minz = 1e9
|
||||
for xs, ys, zs in xyslist:
|
||||
minz = min(minz, min(zs))
|
||||
return minz
|
||||
|
||||
@artist.allow_rasterization
|
||||
def draw(self, renderer, project=False):
|
||||
if project:
|
||||
self.do_3d_projection(renderer)
|
||||
LineCollection.draw(self, renderer)
|
||||
|
||||
|
||||
def line_collection_2d_to_3d(col, zs=0, zdir='z'):
|
||||
"""Convert a LineCollection to a Line3DCollection object."""
|
||||
segments3d = _paths_to_3d_segments(col.get_paths(), zs, zdir)
|
||||
col.__class__ = Line3DCollection
|
||||
col.set_segments(segments3d)
|
||||
|
||||
|
||||
class Patch3D(Patch):
|
||||
"""
|
||||
3D patch object.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, zs=(), zdir='z', **kwargs):
|
||||
Patch.__init__(self, *args, **kwargs)
|
||||
self.set_3d_properties(zs, zdir)
|
||||
|
||||
def set_3d_properties(self, verts, zs=0, zdir='z'):
|
||||
zs = np.broadcast_to(zs, len(verts))
|
||||
self._segment3d = [juggle_axes(x, y, z, zdir)
|
||||
for ((x, y), z) in zip(verts, zs)]
|
||||
self._facecolor3d = Patch.get_facecolor(self)
|
||||
|
||||
def get_path(self):
|
||||
return self._path2d
|
||||
|
||||
def get_facecolor(self):
|
||||
return self._facecolor2d
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
s = self._segment3d
|
||||
xs, ys, zs = zip(*s)
|
||||
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
|
||||
self._path2d = mpath.Path(np.column_stack([vxs, vys]))
|
||||
# FIXME: coloring
|
||||
self._facecolor2d = self._facecolor3d
|
||||
return min(vzs)
|
||||
|
||||
|
||||
class PathPatch3D(Patch3D):
|
||||
"""
|
||||
3D PathPatch object.
|
||||
"""
|
||||
|
||||
def __init__(self, path, *, zs=(), zdir='z', **kwargs):
|
||||
Patch.__init__(self, **kwargs)
|
||||
self.set_3d_properties(path, zs, zdir)
|
||||
|
||||
def set_3d_properties(self, path, zs=0, zdir='z'):
|
||||
Patch3D.set_3d_properties(self, path.vertices, zs=zs, zdir=zdir)
|
||||
self._code3d = path.codes
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
s = self._segment3d
|
||||
xs, ys, zs = zip(*s)
|
||||
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
|
||||
self._path2d = mpath.Path(np.column_stack([vxs, vys]), self._code3d)
|
||||
# FIXME: coloring
|
||||
self._facecolor2d = self._facecolor3d
|
||||
return min(vzs)
|
||||
|
||||
|
||||
def _get_patch_verts(patch):
|
||||
"""Return a list of vertices for the path of a patch."""
|
||||
trans = patch.get_patch_transform()
|
||||
path = patch.get_path()
|
||||
polygons = path.to_polygons(trans)
|
||||
if len(polygons):
|
||||
return polygons[0]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def patch_2d_to_3d(patch, z=0, zdir='z'):
|
||||
"""Convert a Patch to a Patch3D object."""
|
||||
verts = _get_patch_verts(patch)
|
||||
patch.__class__ = Patch3D
|
||||
patch.set_3d_properties(verts, z, zdir)
|
||||
|
||||
|
||||
def pathpatch_2d_to_3d(pathpatch, z=0, zdir='z'):
|
||||
"""Convert a PathPatch to a PathPatch3D object."""
|
||||
path = pathpatch.get_path()
|
||||
trans = pathpatch.get_patch_transform()
|
||||
|
||||
mpath = trans.transform_path(path)
|
||||
pathpatch.__class__ = PathPatch3D
|
||||
pathpatch.set_3d_properties(mpath, z, zdir)
|
||||
|
||||
|
||||
class Patch3DCollection(PatchCollection):
|
||||
"""
|
||||
A collection of 3D patches.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs):
|
||||
"""
|
||||
Create a collection of flat 3D patches with its normal vector
|
||||
pointed in *zdir* direction, and located at *zs* on the *zdir*
|
||||
axis. 'zs' can be a scalar or an array-like of the same length as
|
||||
the number of patches in the collection.
|
||||
|
||||
Constructor arguments are the same as for
|
||||
:class:`~matplotlib.collections.PatchCollection`. In addition,
|
||||
keywords *zs=0* and *zdir='z'* are available.
|
||||
|
||||
Also, the keyword argument "depthshade" is available to
|
||||
indicate whether or not to shade the patches in order to
|
||||
give the appearance of depth (default is *True*).
|
||||
This is typically desired in scatter plots.
|
||||
"""
|
||||
self._depthshade = depthshade
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_3d_properties(zs, zdir)
|
||||
|
||||
def set_sort_zpos(self, val):
|
||||
"""Set the position to use for z-sorting."""
|
||||
self._sort_zpos = val
|
||||
self.stale = True
|
||||
|
||||
def set_3d_properties(self, zs, zdir):
|
||||
# Force the collection to initialize the face and edgecolors
|
||||
# just in case it is a scalarmappable with a colormap.
|
||||
self.update_scalarmappable()
|
||||
offsets = self.get_offsets()
|
||||
if len(offsets) > 0:
|
||||
xs, ys = offsets.T
|
||||
else:
|
||||
xs = []
|
||||
ys = []
|
||||
self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir)
|
||||
self._facecolor3d = self.get_facecolor()
|
||||
self._edgecolor3d = self.get_edgecolor()
|
||||
self.stale = True
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
xs, ys, zs = self._offsets3d
|
||||
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
|
||||
|
||||
fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else
|
||||
self._facecolor3d)
|
||||
fcs = mcolors.to_rgba_array(fcs, self._alpha)
|
||||
self.set_facecolors(fcs)
|
||||
|
||||
ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else
|
||||
self._edgecolor3d)
|
||||
ecs = mcolors.to_rgba_array(ecs, self._alpha)
|
||||
self.set_edgecolors(ecs)
|
||||
PatchCollection.set_offsets(self, np.column_stack([vxs, vys]))
|
||||
|
||||
if vzs.size > 0:
|
||||
return min(vzs)
|
||||
else:
|
||||
return np.nan
|
||||
|
||||
|
||||
class Path3DCollection(PathCollection):
|
||||
"""
|
||||
A collection of 3D paths.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, zs=0, zdir='z', depthshade=True, **kwargs):
|
||||
"""
|
||||
Create a collection of flat 3D paths with its normal vector
|
||||
pointed in *zdir* direction, and located at *zs* on the *zdir*
|
||||
axis. 'zs' can be a scalar or an array-like of the same length as
|
||||
the number of paths in the collection.
|
||||
|
||||
Constructor arguments are the same as for
|
||||
:class:`~matplotlib.collections.PathCollection`. In addition,
|
||||
keywords *zs=0* and *zdir='z'* are available.
|
||||
|
||||
Also, the keyword argument "depthshade" is available to
|
||||
indicate whether or not to shade the patches in order to
|
||||
give the appearance of depth (default is *True*).
|
||||
This is typically desired in scatter plots.
|
||||
"""
|
||||
self._depthshade = depthshade
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_3d_properties(zs, zdir)
|
||||
|
||||
def set_sort_zpos(self, val):
|
||||
"""Set the position to use for z-sorting."""
|
||||
self._sort_zpos = val
|
||||
self.stale = True
|
||||
|
||||
def set_3d_properties(self, zs, zdir):
|
||||
# Force the collection to initialize the face and edgecolors
|
||||
# just in case it is a scalarmappable with a colormap.
|
||||
self.update_scalarmappable()
|
||||
offsets = self.get_offsets()
|
||||
if len(offsets) > 0:
|
||||
xs, ys = offsets.T
|
||||
else:
|
||||
xs = []
|
||||
ys = []
|
||||
self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir)
|
||||
self._facecolor3d = self.get_facecolor()
|
||||
self._edgecolor3d = self.get_edgecolor()
|
||||
self._sizes3d = self.get_sizes()
|
||||
self._linewidth3d = self.get_linewidth()
|
||||
self.stale = True
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
xs, ys, zs = self._offsets3d
|
||||
vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M)
|
||||
|
||||
fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else
|
||||
self._facecolor3d)
|
||||
ecs = (_zalpha(self._edgecolor3d, vzs) if self._depthshade else
|
||||
self._edgecolor3d)
|
||||
sizes = self._sizes3d
|
||||
lws = self._linewidth3d
|
||||
|
||||
# Sort the points based on z coordinates
|
||||
# Performance optimization: Create a sorted index array and reorder
|
||||
# points and point properties according to the index array
|
||||
z_markers_idx = np.argsort(vzs)[::-1]
|
||||
|
||||
# Re-order items
|
||||
vzs = vzs[z_markers_idx]
|
||||
vxs = vxs[z_markers_idx]
|
||||
vys = vys[z_markers_idx]
|
||||
if len(fcs) > 1:
|
||||
fcs = fcs[z_markers_idx]
|
||||
if len(ecs) > 1:
|
||||
ecs = ecs[z_markers_idx]
|
||||
if len(sizes) > 1:
|
||||
sizes = sizes[z_markers_idx]
|
||||
if len(lws) > 1:
|
||||
lws = lws[z_markers_idx]
|
||||
vps = np.column_stack((vxs, vys))
|
||||
|
||||
fcs = mcolors.to_rgba_array(fcs, self._alpha)
|
||||
ecs = mcolors.to_rgba_array(ecs, self._alpha)
|
||||
|
||||
self.set_edgecolors(ecs)
|
||||
self.set_facecolors(fcs)
|
||||
self.set_sizes(sizes)
|
||||
self.set_linewidth(lws)
|
||||
|
||||
PathCollection.set_offsets(self, vps)
|
||||
|
||||
return np.min(vzs) if vzs.size else np.nan
|
||||
|
||||
|
||||
def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True):
|
||||
"""
|
||||
Convert a :class:`~matplotlib.collections.PatchCollection` into a
|
||||
:class:`Patch3DCollection` object
|
||||
(or a :class:`~matplotlib.collections.PathCollection` into a
|
||||
:class:`Path3DCollection` object).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
za
|
||||
The location or locations to place the patches in the collection along
|
||||
the *zdir* axis. Default: 0.
|
||||
zdir
|
||||
The axis in which to place the patches. Default: "z".
|
||||
depthshade
|
||||
Whether to shade the patches to give a sense of depth. Default: *True*.
|
||||
|
||||
"""
|
||||
if isinstance(col, PathCollection):
|
||||
col.__class__ = Path3DCollection
|
||||
elif isinstance(col, PatchCollection):
|
||||
col.__class__ = Patch3DCollection
|
||||
col._depthshade = depthshade
|
||||
col.set_3d_properties(zs, zdir)
|
||||
|
||||
|
||||
class Poly3DCollection(PolyCollection):
|
||||
"""
|
||||
A collection of 3D polygons.
|
||||
|
||||
.. note::
|
||||
**Filling of 3D polygons**
|
||||
|
||||
There is no simple definition of the enclosed surface of a 3D polygon
|
||||
unless the polygon is planar.
|
||||
|
||||
In practice, Matplotlib fills the 2D projection of the polygon. This
|
||||
gives a correct filling appearance only for planar polygons. For all
|
||||
other polygons, you'll find orientations in which the edges of the
|
||||
polygon intersect in the projection. This will lead to an incorrect
|
||||
visualization of the 3D area.
|
||||
|
||||
If you need filled areas, it is recommended to create them via
|
||||
`~mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf`, which creates a
|
||||
triangulation and thus generates consistent surfaces.
|
||||
"""
|
||||
|
||||
def __init__(self, verts, *args, zsort='average', **kwargs):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
verts : list of array-like Nx3
|
||||
Each element describes a polygon as a sequence of ``N_i`` points
|
||||
``(x, y, z)``.
|
||||
zsort : {'average', 'min', 'max'}, default: 'average'
|
||||
The calculation method for the z-order.
|
||||
See `~.Poly3DCollection.set_zsort` for details.
|
||||
*args, **kwargs
|
||||
All other parameters are forwarded to `.PolyCollection`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Note that this class does a bit of magic with the _facecolors
|
||||
and _edgecolors properties.
|
||||
"""
|
||||
super().__init__(verts, *args, **kwargs)
|
||||
self.set_zsort(zsort)
|
||||
self._codes3d = None
|
||||
|
||||
_zsort_functions = {
|
||||
'average': np.average,
|
||||
'min': np.min,
|
||||
'max': np.max,
|
||||
}
|
||||
|
||||
def set_zsort(self, zsort):
|
||||
"""
|
||||
Set the calculation method for the z-order.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
zsort : {'average', 'min', 'max'}
|
||||
The function applied on the z-coordinates of the vertices in the
|
||||
viewer's coordinate system, to determine the z-order.
|
||||
"""
|
||||
self._zsortfunc = self._zsort_functions[zsort]
|
||||
self._sort_zpos = None
|
||||
self.stale = True
|
||||
|
||||
def get_vector(self, segments3d):
|
||||
"""Optimize points for projection."""
|
||||
if len(segments3d):
|
||||
xs, ys, zs = np.row_stack(segments3d).T
|
||||
else: # row_stack can't stack zero arrays.
|
||||
xs, ys, zs = [], [], []
|
||||
ones = np.ones(len(xs))
|
||||
self._vec = np.array([xs, ys, zs, ones])
|
||||
|
||||
indices = [0, *np.cumsum([len(segment) for segment in segments3d])]
|
||||
self._segslices = [*map(slice, indices[:-1], indices[1:])]
|
||||
|
||||
def set_verts(self, verts, closed=True):
|
||||
"""Set 3D vertices."""
|
||||
self.get_vector(verts)
|
||||
# 2D verts will be updated at draw time
|
||||
PolyCollection.set_verts(self, [], False)
|
||||
self._closed = closed
|
||||
|
||||
def set_verts_and_codes(self, verts, codes):
|
||||
"""Set 3D vertices with path codes."""
|
||||
# set vertices with closed=False to prevent PolyCollection from
|
||||
# setting path codes
|
||||
self.set_verts(verts, closed=False)
|
||||
# and set our own codes instead.
|
||||
self._codes3d = codes
|
||||
|
||||
def set_3d_properties(self):
|
||||
# Force the collection to initialize the face and edgecolors
|
||||
# just in case it is a scalarmappable with a colormap.
|
||||
self.update_scalarmappable()
|
||||
self._sort_zpos = None
|
||||
self.set_zsort('average')
|
||||
self._facecolors3d = PolyCollection.get_facecolor(self)
|
||||
self._edgecolors3d = PolyCollection.get_edgecolor(self)
|
||||
self._alpha3d = PolyCollection.get_alpha(self)
|
||||
self.stale = True
|
||||
|
||||
def set_sort_zpos(self, val):
|
||||
"""Set the position to use for z-sorting."""
|
||||
self._sort_zpos = val
|
||||
self.stale = True
|
||||
|
||||
def do_3d_projection(self, renderer):
|
||||
"""
|
||||
Perform the 3D projection for this object.
|
||||
"""
|
||||
# FIXME: This may no longer be needed?
|
||||
if self._A is not None:
|
||||
self.update_scalarmappable()
|
||||
self._facecolors3d = self._facecolors
|
||||
|
||||
txs, tys, tzs = proj3d._proj_transform_vec(self._vec, renderer.M)
|
||||
xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices]
|
||||
|
||||
# This extra fuss is to re-order face / edge colors
|
||||
cface = self._facecolors3d
|
||||
cedge = self._edgecolors3d
|
||||
if len(cface) != len(xyzlist):
|
||||
cface = cface.repeat(len(xyzlist), axis=0)
|
||||
if len(cedge) != len(xyzlist):
|
||||
if len(cedge) == 0:
|
||||
cedge = cface
|
||||
else:
|
||||
cedge = cedge.repeat(len(xyzlist), axis=0)
|
||||
|
||||
# sort by depth (furthest drawn first)
|
||||
z_segments_2d = sorted(
|
||||
((self._zsortfunc(zs), np.column_stack([xs, ys]), fc, ec, idx)
|
||||
for idx, ((xs, ys, zs), fc, ec)
|
||||
in enumerate(zip(xyzlist, cface, cedge))),
|
||||
key=lambda x: x[0], reverse=True)
|
||||
|
||||
zzs, segments_2d, self._facecolors2d, self._edgecolors2d, idxs = \
|
||||
zip(*z_segments_2d)
|
||||
|
||||
if self._codes3d is not None:
|
||||
codes = [self._codes3d[idx] for idx in idxs]
|
||||
PolyCollection.set_verts_and_codes(self, segments_2d, codes)
|
||||
else:
|
||||
PolyCollection.set_verts(self, segments_2d, self._closed)
|
||||
|
||||
if len(self._edgecolors3d) != len(cface):
|
||||
self._edgecolors2d = self._edgecolors3d
|
||||
|
||||
# Return zorder value
|
||||
if self._sort_zpos is not None:
|
||||
zvec = np.array([[0], [0], [self._sort_zpos], [1]])
|
||||
ztrans = proj3d._proj_transform_vec(zvec, renderer.M)
|
||||
return ztrans[2][0]
|
||||
elif tzs.size > 0:
|
||||
# FIXME: Some results still don't look quite right.
|
||||
# In particular, examine contourf3d_demo2.py
|
||||
# with az = -54 and elev = -45.
|
||||
return np.min(tzs)
|
||||
else:
|
||||
return np.nan
|
||||
|
||||
def set_facecolor(self, colors):
|
||||
PolyCollection.set_facecolor(self, colors)
|
||||
self._facecolors3d = PolyCollection.get_facecolor(self)
|
||||
|
||||
def set_edgecolor(self, colors):
|
||||
PolyCollection.set_edgecolor(self, colors)
|
||||
self._edgecolors3d = PolyCollection.get_edgecolor(self)
|
||||
|
||||
def set_alpha(self, alpha):
|
||||
# docstring inherited
|
||||
artist.Artist.set_alpha(self, alpha)
|
||||
try:
|
||||
self._facecolors3d = mcolors.to_rgba_array(
|
||||
self._facecolors3d, self._alpha)
|
||||
except (AttributeError, TypeError, IndexError):
|
||||
pass
|
||||
try:
|
||||
self._edgecolors = mcolors.to_rgba_array(
|
||||
self._edgecolors3d, self._alpha)
|
||||
except (AttributeError, TypeError, IndexError):
|
||||
pass
|
||||
self.stale = True
|
||||
|
||||
def get_facecolor(self):
|
||||
return self._facecolors2d
|
||||
|
||||
def get_edgecolor(self):
|
||||
return self._edgecolors2d
|
||||
|
||||
|
||||
def poly_collection_2d_to_3d(col, zs=0, zdir='z'):
|
||||
"""Convert a PolyCollection to a Poly3DCollection object."""
|
||||
segments_3d, codes = _paths_to_3d_segments_with_codes(
|
||||
col.get_paths(), zs, zdir)
|
||||
col.__class__ = Poly3DCollection
|
||||
col.set_verts_and_codes(segments_3d, codes)
|
||||
col.set_3d_properties()
|
||||
|
||||
|
||||
def juggle_axes(xs, ys, zs, zdir):
|
||||
"""
|
||||
Reorder coordinates so that 2D xs, ys can be plotted in the plane
|
||||
orthogonal to zdir. zdir is normally x, y or z. However, if zdir
|
||||
starts with a '-' it is interpreted as a compensation for rotate_axes.
|
||||
"""
|
||||
if zdir == 'x':
|
||||
return zs, xs, ys
|
||||
elif zdir == 'y':
|
||||
return xs, zs, ys
|
||||
elif zdir[0] == '-':
|
||||
return rotate_axes(xs, ys, zs, zdir)
|
||||
else:
|
||||
return xs, ys, zs
|
||||
|
||||
|
||||
def rotate_axes(xs, ys, zs, zdir):
|
||||
"""
|
||||
Reorder coordinates so that the axes are rotated with zdir along
|
||||
the original z axis. Prepending the axis with a '-' does the
|
||||
inverse transform, so zdir can be x, -x, y, -y, z or -z
|
||||
"""
|
||||
if zdir == 'x':
|
||||
return ys, zs, xs
|
||||
elif zdir == '-x':
|
||||
return zs, xs, ys
|
||||
|
||||
elif zdir == 'y':
|
||||
return zs, xs, ys
|
||||
elif zdir == '-y':
|
||||
return ys, zs, xs
|
||||
|
||||
else:
|
||||
return xs, ys, zs
|
||||
|
||||
|
||||
def _get_colors(c, num):
|
||||
"""Stretch the color argument to provide the required number *num*."""
|
||||
return np.broadcast_to(
|
||||
mcolors.to_rgba_array(c) if len(c) else [0, 0, 0, 0],
|
||||
(num, 4))
|
||||
|
||||
|
||||
def _zalpha(colors, zs):
|
||||
"""Modify the alphas of the color list according to depth."""
|
||||
# FIXME: This only works well if the points for *zs* are well-spaced
|
||||
# in all three dimensions. Otherwise, at certain orientations,
|
||||
# the min and max zs are very close together.
|
||||
# Should really normalize against the viewing depth.
|
||||
if len(zs) == 0:
|
||||
return np.zeros((0, 4))
|
||||
norm = Normalize(min(zs), max(zs))
|
||||
sats = 1 - norm(zs) * 0.7
|
||||
rgba = np.broadcast_to(mcolors.to_rgba_array(colors), (len(zs), 4))
|
||||
return np.column_stack([rgba[:, :3], rgba[:, 3] * sats])
|
2955
venv/Lib/site-packages/mpl_toolkits/mplot3d/axes3d.py
Normal file
2955
venv/Lib/site-packages/mpl_toolkits/mplot3d/axes3d.py
Normal file
File diff suppressed because it is too large
Load diff
488
venv/Lib/site-packages/mpl_toolkits/mplot3d/axis3d.py
Normal file
488
venv/Lib/site-packages/mpl_toolkits/mplot3d/axis3d.py
Normal file
|
@ -0,0 +1,488 @@
|
|||
# axis3d.py, original mplot3d version by John Porter
|
||||
# Created: 23 Sep 2005
|
||||
# Parts rewritten by Reinier Heeres <reinier@heeres.eu>
|
||||
|
||||
import numpy as np
|
||||
|
||||
import matplotlib.transforms as mtransforms
|
||||
from matplotlib import (
|
||||
artist, lines as mlines, axis as maxis, patches as mpatches, rcParams)
|
||||
from . import art3d, proj3d
|
||||
|
||||
|
||||
def move_from_center(coord, centers, deltas, axmask=(True, True, True)):
|
||||
"""
|
||||
For each coordinate where *axmask* is True, move *coord* away from
|
||||
*centers* by *deltas*.
|
||||
"""
|
||||
coord = np.asarray(coord)
|
||||
return coord + axmask * np.copysign(1, coord - centers) * deltas
|
||||
|
||||
|
||||
def tick_update_position(tick, tickxs, tickys, labelpos):
|
||||
"""Update tick line and label position and style."""
|
||||
|
||||
tick.label1.set_position(labelpos)
|
||||
tick.label2.set_position(labelpos)
|
||||
tick.tick1line.set_visible(True)
|
||||
tick.tick2line.set_visible(False)
|
||||
tick.tick1line.set_linestyle('-')
|
||||
tick.tick1line.set_marker('')
|
||||
tick.tick1line.set_data(tickxs, tickys)
|
||||
tick.gridline.set_data(0, 0)
|
||||
|
||||
|
||||
class Axis(maxis.XAxis):
|
||||
"""An Axis class for the 3D plots."""
|
||||
# These points from the unit cube make up the x, y and z-planes
|
||||
_PLANES = (
|
||||
(0, 3, 7, 4), (1, 2, 6, 5), # yz planes
|
||||
(0, 1, 5, 4), (3, 2, 6, 7), # xz planes
|
||||
(0, 1, 2, 3), (4, 5, 6, 7), # xy planes
|
||||
)
|
||||
|
||||
# Some properties for the axes
|
||||
_AXINFO = {
|
||||
'x': {'i': 0, 'tickdir': 1, 'juggled': (1, 0, 2),
|
||||
'color': (0.95, 0.95, 0.95, 0.5)},
|
||||
'y': {'i': 1, 'tickdir': 0, 'juggled': (0, 1, 2),
|
||||
'color': (0.90, 0.90, 0.90, 0.5)},
|
||||
'z': {'i': 2, 'tickdir': 0, 'juggled': (0, 2, 1),
|
||||
'color': (0.925, 0.925, 0.925, 0.5)},
|
||||
}
|
||||
|
||||
def __init__(self, adir, v_intervalx, d_intervalx, axes, *args,
|
||||
rotate_label=None, **kwargs):
|
||||
# adir identifies which axes this is
|
||||
self.adir = adir
|
||||
|
||||
# This is a temporary member variable.
|
||||
# Do not depend on this existing in future releases!
|
||||
self._axinfo = self._AXINFO[adir].copy()
|
||||
if rcParams['_internal.classic_mode']:
|
||||
self._axinfo.update({
|
||||
'label': {'va': 'center', 'ha': 'center'},
|
||||
'tick': {
|
||||
'inward_factor': 0.2,
|
||||
'outward_factor': 0.1,
|
||||
'linewidth': {
|
||||
True: rcParams['lines.linewidth'], # major
|
||||
False: rcParams['lines.linewidth'], # minor
|
||||
}
|
||||
},
|
||||
'axisline': {'linewidth': 0.75, 'color': (0, 0, 0, 1)},
|
||||
'grid': {
|
||||
'color': (0.9, 0.9, 0.9, 1),
|
||||
'linewidth': 1.0,
|
||||
'linestyle': '-',
|
||||
},
|
||||
})
|
||||
else:
|
||||
self._axinfo.update({
|
||||
'label': {'va': 'center', 'ha': 'center'},
|
||||
'tick': {
|
||||
'inward_factor': 0.2,
|
||||
'outward_factor': 0.1,
|
||||
'linewidth': {
|
||||
True: ( # major
|
||||
rcParams['xtick.major.width'] if adir in 'xz' else
|
||||
rcParams['ytick.major.width']),
|
||||
False: ( # minor
|
||||
rcParams['xtick.minor.width'] if adir in 'xz' else
|
||||
rcParams['ytick.minor.width']),
|
||||
}
|
||||
},
|
||||
'axisline': {
|
||||
'linewidth': rcParams['axes.linewidth'],
|
||||
'color': rcParams['axes.edgecolor'],
|
||||
},
|
||||
'grid': {
|
||||
'color': rcParams['grid.color'],
|
||||
'linewidth': rcParams['grid.linewidth'],
|
||||
'linestyle': rcParams['grid.linestyle'],
|
||||
},
|
||||
})
|
||||
|
||||
maxis.XAxis.__init__(self, axes, *args, **kwargs)
|
||||
|
||||
# data and viewing intervals for this direction
|
||||
self.d_interval = d_intervalx
|
||||
self.v_interval = v_intervalx
|
||||
self.set_rotate_label(rotate_label)
|
||||
|
||||
def init3d(self):
|
||||
self.line = mlines.Line2D(
|
||||
xdata=(0, 0), ydata=(0, 0),
|
||||
linewidth=self._axinfo['axisline']['linewidth'],
|
||||
color=self._axinfo['axisline']['color'],
|
||||
antialiased=True)
|
||||
|
||||
# Store dummy data in Polygon object
|
||||
self.pane = mpatches.Polygon(
|
||||
np.array([[0, 0], [0, 1], [1, 0], [0, 0]]),
|
||||
closed=False, alpha=0.8, facecolor='k', edgecolor='k')
|
||||
self.set_pane_color(self._axinfo['color'])
|
||||
|
||||
self.axes._set_artist_props(self.line)
|
||||
self.axes._set_artist_props(self.pane)
|
||||
self.gridlines = art3d.Line3DCollection([])
|
||||
self.axes._set_artist_props(self.gridlines)
|
||||
self.axes._set_artist_props(self.label)
|
||||
self.axes._set_artist_props(self.offsetText)
|
||||
# Need to be able to place the label at the correct location
|
||||
self.label._transform = self.axes.transData
|
||||
self.offsetText._transform = self.axes.transData
|
||||
|
||||
def get_major_ticks(self, numticks=None):
|
||||
ticks = maxis.XAxis.get_major_ticks(self, numticks)
|
||||
for t in ticks:
|
||||
for obj in [
|
||||
t.tick1line, t.tick2line, t.gridline, t.label1, t.label2]:
|
||||
obj.set_transform(self.axes.transData)
|
||||
return ticks
|
||||
|
||||
def get_minor_ticks(self, numticks=None):
|
||||
ticks = maxis.XAxis.get_minor_ticks(self, numticks)
|
||||
for t in ticks:
|
||||
for obj in [
|
||||
t.tick1line, t.tick2line, t.gridline, t.label1, t.label2]:
|
||||
obj.set_transform(self.axes.transData)
|
||||
return ticks
|
||||
|
||||
def set_pane_pos(self, xys):
|
||||
xys = np.asarray(xys)
|
||||
xys = xys[:, :2]
|
||||
self.pane.xy = xys
|
||||
self.stale = True
|
||||
|
||||
def set_pane_color(self, color):
|
||||
"""Set pane color to a RGBA tuple."""
|
||||
self._axinfo['color'] = color
|
||||
self.pane.set_edgecolor(color)
|
||||
self.pane.set_facecolor(color)
|
||||
self.pane.set_alpha(color[-1])
|
||||
self.stale = True
|
||||
|
||||
def set_rotate_label(self, val):
|
||||
"""
|
||||
Whether to rotate the axis label: True, False or None.
|
||||
If set to None the label will be rotated if longer than 4 chars.
|
||||
"""
|
||||
self._rotate_label = val
|
||||
self.stale = True
|
||||
|
||||
def get_rotate_label(self, text):
|
||||
if self._rotate_label is not None:
|
||||
return self._rotate_label
|
||||
else:
|
||||
return len(text) > 4
|
||||
|
||||
def _get_coord_info(self, renderer):
|
||||
mins, maxs = np.array([
|
||||
self.axes.get_xbound(),
|
||||
self.axes.get_ybound(),
|
||||
self.axes.get_zbound(),
|
||||
]).T
|
||||
centers = (maxs + mins) / 2.
|
||||
deltas = (maxs - mins) / 12.
|
||||
mins = mins - deltas / 4.
|
||||
maxs = maxs + deltas / 4.
|
||||
|
||||
vals = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2]
|
||||
tc = self.axes.tunit_cube(vals, renderer.M)
|
||||
avgz = [tc[p1][2] + tc[p2][2] + tc[p3][2] + tc[p4][2]
|
||||
for p1, p2, p3, p4 in self._PLANES]
|
||||
highs = np.array([avgz[2*i] < avgz[2*i+1] for i in range(3)])
|
||||
|
||||
return mins, maxs, centers, deltas, tc, highs
|
||||
|
||||
def draw_pane(self, renderer):
|
||||
renderer.open_group('pane3d', gid=self.get_gid())
|
||||
|
||||
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
|
||||
|
||||
info = self._axinfo
|
||||
index = info['i']
|
||||
if not highs[index]:
|
||||
plane = self._PLANES[2 * index]
|
||||
else:
|
||||
plane = self._PLANES[2 * index + 1]
|
||||
xys = [tc[p] for p in plane]
|
||||
self.set_pane_pos(xys)
|
||||
self.pane.draw(renderer)
|
||||
|
||||
renderer.close_group('pane3d')
|
||||
|
||||
@artist.allow_rasterization
|
||||
def draw(self, renderer):
|
||||
self.label._transform = self.axes.transData
|
||||
renderer.open_group('axis3d', gid=self.get_gid())
|
||||
|
||||
ticks = self._update_ticks()
|
||||
|
||||
info = self._axinfo
|
||||
index = info['i']
|
||||
|
||||
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
|
||||
|
||||
# Determine grid lines
|
||||
minmax = np.where(highs, maxs, mins)
|
||||
maxmin = np.where(highs, mins, maxs)
|
||||
|
||||
# Draw main axis line
|
||||
juggled = info['juggled']
|
||||
edgep1 = minmax.copy()
|
||||
edgep1[juggled[0]] = maxmin[juggled[0]]
|
||||
|
||||
edgep2 = edgep1.copy()
|
||||
edgep2[juggled[1]] = maxmin[juggled[1]]
|
||||
pep = np.asarray(
|
||||
proj3d.proj_trans_points([edgep1, edgep2], renderer.M))
|
||||
centpt = proj3d.proj_transform(*centers, renderer.M)
|
||||
self.line.set_data(pep[0], pep[1])
|
||||
self.line.draw(renderer)
|
||||
|
||||
# Grid points where the planes meet
|
||||
xyz0 = np.tile(minmax, (len(ticks), 1))
|
||||
xyz0[:, index] = [tick.get_loc() for tick in ticks]
|
||||
|
||||
# Draw labels
|
||||
# The transAxes transform is used because the Text object
|
||||
# rotates the text relative to the display coordinate system.
|
||||
# Therefore, if we want the labels to remain parallel to the
|
||||
# axis regardless of the aspect ratio, we need to convert the
|
||||
# edge points of the plane to display coordinates and calculate
|
||||
# an angle from that.
|
||||
# TODO: Maybe Text objects should handle this themselves?
|
||||
dx, dy = (self.axes.transAxes.transform([pep[0:2, 1]]) -
|
||||
self.axes.transAxes.transform([pep[0:2, 0]]))[0]
|
||||
|
||||
lxyz = 0.5 * (edgep1 + edgep2)
|
||||
|
||||
# A rough estimate; points are ambiguous since 3D plots rotate
|
||||
ax_scale = self.axes.bbox.size / self.figure.bbox.size
|
||||
ax_inches = np.multiply(ax_scale, self.figure.get_size_inches())
|
||||
ax_points_estimate = sum(72. * ax_inches)
|
||||
deltas_per_point = 48 / ax_points_estimate
|
||||
default_offset = 21.
|
||||
labeldeltas = (
|
||||
(self.labelpad + default_offset) * deltas_per_point * deltas)
|
||||
axmask = [True, True, True]
|
||||
axmask[index] = False
|
||||
lxyz = move_from_center(lxyz, centers, labeldeltas, axmask)
|
||||
tlx, tly, tlz = proj3d.proj_transform(*lxyz, renderer.M)
|
||||
self.label.set_position((tlx, tly))
|
||||
if self.get_rotate_label(self.label.get_text()):
|
||||
angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx)))
|
||||
self.label.set_rotation(angle)
|
||||
self.label.set_va(info['label']['va'])
|
||||
self.label.set_ha(info['label']['ha'])
|
||||
self.label.draw(renderer)
|
||||
|
||||
# Draw Offset text
|
||||
|
||||
# Which of the two edge points do we want to
|
||||
# use for locating the offset text?
|
||||
if juggled[2] == 2:
|
||||
outeredgep = edgep1
|
||||
outerindex = 0
|
||||
else:
|
||||
outeredgep = edgep2
|
||||
outerindex = 1
|
||||
|
||||
pos = move_from_center(outeredgep, centers, labeldeltas, axmask)
|
||||
olx, oly, olz = proj3d.proj_transform(*pos, renderer.M)
|
||||
self.offsetText.set_text(self.major.formatter.get_offset())
|
||||
self.offsetText.set_position((olx, oly))
|
||||
angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx)))
|
||||
self.offsetText.set_rotation(angle)
|
||||
# Must set rotation mode to "anchor" so that
|
||||
# the alignment point is used as the "fulcrum" for rotation.
|
||||
self.offsetText.set_rotation_mode('anchor')
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
# Note: the following statement for determining the proper alignment of
|
||||
# the offset text. This was determined entirely by trial-and-error
|
||||
# and should not be in any way considered as "the way". There are
|
||||
# still some edge cases where alignment is not quite right, but this
|
||||
# seems to be more of a geometry issue (in other words, I might be
|
||||
# using the wrong reference points).
|
||||
#
|
||||
# (TT, FF, TF, FT) are the shorthand for the tuple of
|
||||
# (centpt[info['tickdir']] <= pep[info['tickdir'], outerindex],
|
||||
# centpt[index] <= pep[index, outerindex])
|
||||
#
|
||||
# Three-letters (e.g., TFT, FTT) are short-hand for the array of bools
|
||||
# from the variable 'highs'.
|
||||
# ---------------------------------------------------------------------
|
||||
if centpt[info['tickdir']] > pep[info['tickdir'], outerindex]:
|
||||
# if FT and if highs has an even number of Trues
|
||||
if (centpt[index] <= pep[index, outerindex]
|
||||
and np.count_nonzero(highs) % 2 == 0):
|
||||
# Usually, this means align right, except for the FTT case,
|
||||
# in which offset for axis 1 and 2 are aligned left.
|
||||
if highs.tolist() == [False, True, True] and index in (1, 2):
|
||||
align = 'left'
|
||||
else:
|
||||
align = 'right'
|
||||
else:
|
||||
# The FF case
|
||||
align = 'left'
|
||||
else:
|
||||
# if TF and if highs has an even number of Trues
|
||||
if (centpt[index] > pep[index, outerindex]
|
||||
and np.count_nonzero(highs) % 2 == 0):
|
||||
# Usually mean align left, except if it is axis 2
|
||||
if index == 2:
|
||||
align = 'right'
|
||||
else:
|
||||
align = 'left'
|
||||
else:
|
||||
# The TT case
|
||||
align = 'right'
|
||||
|
||||
self.offsetText.set_va('center')
|
||||
self.offsetText.set_ha(align)
|
||||
self.offsetText.draw(renderer)
|
||||
|
||||
if self.axes._draw_grid and len(ticks):
|
||||
# Grid lines go from the end of one plane through the plane
|
||||
# intersection (at xyz0) to the end of the other plane. The first
|
||||
# point (0) differs along dimension index-2 and the last (2) along
|
||||
# dimension index-1.
|
||||
lines = np.stack([xyz0, xyz0, xyz0], axis=1)
|
||||
lines[:, 0, index - 2] = maxmin[index - 2]
|
||||
lines[:, 2, index - 1] = maxmin[index - 1]
|
||||
self.gridlines.set_segments(lines)
|
||||
self.gridlines.set_color(info['grid']['color'])
|
||||
self.gridlines.set_linewidth(info['grid']['linewidth'])
|
||||
self.gridlines.set_linestyle(info['grid']['linestyle'])
|
||||
self.gridlines.draw(renderer, project=True)
|
||||
|
||||
# Draw ticks
|
||||
tickdir = info['tickdir']
|
||||
tickdelta = deltas[tickdir]
|
||||
if highs[tickdir]:
|
||||
ticksign = 1
|
||||
else:
|
||||
ticksign = -1
|
||||
|
||||
for tick in ticks:
|
||||
# Get tick line positions
|
||||
pos = edgep1.copy()
|
||||
pos[index] = tick.get_loc()
|
||||
pos[tickdir] = (
|
||||
edgep1[tickdir]
|
||||
+ info['tick']['outward_factor'] * ticksign * tickdelta)
|
||||
x1, y1, z1 = proj3d.proj_transform(*pos, renderer.M)
|
||||
pos[tickdir] = (
|
||||
edgep1[tickdir]
|
||||
- info['tick']['inward_factor'] * ticksign * tickdelta)
|
||||
x2, y2, z2 = proj3d.proj_transform(*pos, renderer.M)
|
||||
|
||||
# Get position of label
|
||||
default_offset = 8. # A rough estimate
|
||||
labeldeltas = (
|
||||
(tick.get_pad() + default_offset) * deltas_per_point * deltas)
|
||||
|
||||
axmask = [True, True, True]
|
||||
axmask[index] = False
|
||||
pos[tickdir] = edgep1[tickdir]
|
||||
pos = move_from_center(pos, centers, labeldeltas, axmask)
|
||||
lx, ly, lz = proj3d.proj_transform(*pos, renderer.M)
|
||||
|
||||
tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly))
|
||||
tick.tick1line.set_linewidth(
|
||||
info['tick']['linewidth'][tick._major])
|
||||
tick.draw(renderer)
|
||||
|
||||
renderer.close_group('axis3d')
|
||||
self.stale = False
|
||||
|
||||
# TODO: Get this to work (more) properly when mplot3d supports the
|
||||
# transforms framework.
|
||||
def get_tightbbox(self, renderer, *, for_layout_only=False):
|
||||
# inherited docstring
|
||||
if not self.get_visible():
|
||||
return
|
||||
# We have to directly access the internal data structures
|
||||
# (and hope they are up to date) because at draw time we
|
||||
# shift the ticks and their labels around in (x, y) space
|
||||
# based on the projection, the current view port, and their
|
||||
# position in 3D space. If we extend the transforms framework
|
||||
# into 3D we would not need to do this different book keeping
|
||||
# than we do in the normal axis
|
||||
major_locs = self.get_majorticklocs()
|
||||
minor_locs = self.get_minorticklocs()
|
||||
|
||||
ticks = [*self.get_minor_ticks(len(minor_locs)),
|
||||
*self.get_major_ticks(len(major_locs))]
|
||||
view_low, view_high = self.get_view_interval()
|
||||
if view_low > view_high:
|
||||
view_low, view_high = view_high, view_low
|
||||
interval_t = self.get_transform().transform([view_low, view_high])
|
||||
|
||||
ticks_to_draw = []
|
||||
for tick in ticks:
|
||||
try:
|
||||
loc_t = self.get_transform().transform(tick.get_loc())
|
||||
except AssertionError:
|
||||
# Transform.transform doesn't allow masked values but
|
||||
# some scales might make them, so we need this try/except.
|
||||
pass
|
||||
else:
|
||||
if mtransforms._interval_contains_close(interval_t, loc_t):
|
||||
ticks_to_draw.append(tick)
|
||||
|
||||
ticks = ticks_to_draw
|
||||
|
||||
bb_1, bb_2 = self._get_tick_bboxes(ticks, renderer)
|
||||
other = []
|
||||
|
||||
if self.line.get_visible():
|
||||
other.append(self.line.get_window_extent(renderer))
|
||||
if (self.label.get_visible() and not for_layout_only and
|
||||
self.label.get_text()):
|
||||
other.append(self.label.get_window_extent(renderer))
|
||||
|
||||
return mtransforms.Bbox.union([*bb_1, *bb_2, *other])
|
||||
|
||||
@property
|
||||
def d_interval(self):
|
||||
return self.get_data_interval()
|
||||
|
||||
@d_interval.setter
|
||||
def d_interval(self, minmax):
|
||||
self.set_data_interval(*minmax)
|
||||
|
||||
@property
|
||||
def v_interval(self):
|
||||
return self.get_view_interval()
|
||||
|
||||
@v_interval.setter
|
||||
def v_interval(self, minmax):
|
||||
self.set_view_interval(*minmax)
|
||||
|
||||
|
||||
# Use classes to look at different data limits
|
||||
|
||||
|
||||
class XAxis(Axis):
|
||||
get_view_interval, set_view_interval = maxis._make_getset_interval(
|
||||
"view", "xy_viewLim", "intervalx")
|
||||
get_data_interval, set_data_interval = maxis._make_getset_interval(
|
||||
"data", "xy_dataLim", "intervalx")
|
||||
|
||||
|
||||
class YAxis(Axis):
|
||||
get_view_interval, set_view_interval = maxis._make_getset_interval(
|
||||
"view", "xy_viewLim", "intervaly")
|
||||
get_data_interval, set_data_interval = maxis._make_getset_interval(
|
||||
"data", "xy_dataLim", "intervaly")
|
||||
|
||||
|
||||
class ZAxis(Axis):
|
||||
get_view_interval, set_view_interval = maxis._make_getset_interval(
|
||||
"view", "zz_viewLim", "intervalx")
|
||||
get_data_interval, set_data_interval = maxis._make_getset_interval(
|
||||
"data", "zz_dataLim", "intervalx")
|
175
venv/Lib/site-packages/mpl_toolkits/mplot3d/proj3d.py
Normal file
175
venv/Lib/site-packages/mpl_toolkits/mplot3d/proj3d.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
Various transforms used for by the 3D code
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import numpy.linalg as linalg
|
||||
|
||||
|
||||
def _line2d_seg_dist(p1, p2, p0):
|
||||
"""
|
||||
Return the distance(s) from line defined by p1 - p2 to point(s) p0.
|
||||
|
||||
p0[0] = x(s)
|
||||
p0[1] = y(s)
|
||||
|
||||
intersection point p = p1 + u*(p2-p1)
|
||||
and intersection point lies within segment if u is between 0 and 1
|
||||
"""
|
||||
|
||||
x21 = p2[0] - p1[0]
|
||||
y21 = p2[1] - p1[1]
|
||||
x01 = np.asarray(p0[0]) - p1[0]
|
||||
y01 = np.asarray(p0[1]) - p1[1]
|
||||
|
||||
u = (x01*x21 + y01*y21) / (x21**2 + y21**2)
|
||||
u = np.clip(u, 0, 1)
|
||||
d = np.hypot(x01 - u*x21, y01 - u*y21)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def world_transformation(xmin, xmax,
|
||||
ymin, ymax,
|
||||
zmin, zmax, pb_aspect=None):
|
||||
"""
|
||||
Produce a matrix that scales homogeneous coords in the specified ranges
|
||||
to [0, 1], or [0, pb_aspect[i]] if the plotbox aspect ratio is specified.
|
||||
"""
|
||||
dx = xmax - xmin
|
||||
dy = ymax - ymin
|
||||
dz = zmax - zmin
|
||||
if pb_aspect is not None:
|
||||
ax, ay, az = pb_aspect
|
||||
dx /= ax
|
||||
dy /= ay
|
||||
dz /= az
|
||||
|
||||
return np.array([[1/dx, 0, 0, -xmin/dx],
|
||||
[0, 1/dy, 0, -ymin/dy],
|
||||
[0, 0, 1/dz, -zmin/dz],
|
||||
[0, 0, 0, 1]])
|
||||
|
||||
|
||||
def view_transformation(E, R, V):
|
||||
n = (E - R)
|
||||
## new
|
||||
# n /= np.linalg.norm(n)
|
||||
# u = np.cross(V, n)
|
||||
# u /= np.linalg.norm(u)
|
||||
# v = np.cross(n, u)
|
||||
# Mr = np.diag([1.] * 4)
|
||||
# Mt = np.diag([1.] * 4)
|
||||
# Mr[:3,:3] = u, v, n
|
||||
# Mt[:3,-1] = -E
|
||||
## end new
|
||||
|
||||
## old
|
||||
n = n / np.linalg.norm(n)
|
||||
u = np.cross(V, n)
|
||||
u = u / np.linalg.norm(u)
|
||||
v = np.cross(n, u)
|
||||
Mr = [[u[0], u[1], u[2], 0],
|
||||
[v[0], v[1], v[2], 0],
|
||||
[n[0], n[1], n[2], 0],
|
||||
[0, 0, 0, 1]]
|
||||
#
|
||||
Mt = [[1, 0, 0, -E[0]],
|
||||
[0, 1, 0, -E[1]],
|
||||
[0, 0, 1, -E[2]],
|
||||
[0, 0, 0, 1]]
|
||||
## end old
|
||||
|
||||
return np.dot(Mr, Mt)
|
||||
|
||||
|
||||
def persp_transformation(zfront, zback):
|
||||
a = (zfront+zback)/(zfront-zback)
|
||||
b = -2*(zfront*zback)/(zfront-zback)
|
||||
return np.array([[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, a, b],
|
||||
[0, 0, -1, 0]])
|
||||
|
||||
|
||||
def ortho_transformation(zfront, zback):
|
||||
# note: w component in the resulting vector will be (zback-zfront), not 1
|
||||
a = -(zfront + zback)
|
||||
b = -(zfront - zback)
|
||||
return np.array([[2, 0, 0, 0],
|
||||
[0, 2, 0, 0],
|
||||
[0, 0, -2, 0],
|
||||
[0, 0, a, b]])
|
||||
|
||||
|
||||
def _proj_transform_vec(vec, M):
|
||||
vecw = np.dot(M, vec)
|
||||
w = vecw[3]
|
||||
# clip here..
|
||||
txs, tys, tzs = vecw[0]/w, vecw[1]/w, vecw[2]/w
|
||||
return txs, tys, tzs
|
||||
|
||||
|
||||
def _proj_transform_vec_clip(vec, M):
|
||||
vecw = np.dot(M, vec)
|
||||
w = vecw[3]
|
||||
# clip here.
|
||||
txs, tys, tzs = vecw[0] / w, vecw[1] / w, vecw[2] / w
|
||||
tis = (0 <= vecw[0]) & (vecw[0] <= 1) & (0 <= vecw[1]) & (vecw[1] <= 1)
|
||||
if np.any(tis):
|
||||
tis = vecw[1] < 1
|
||||
return txs, tys, tzs, tis
|
||||
|
||||
|
||||
def inv_transform(xs, ys, zs, M):
|
||||
iM = linalg.inv(M)
|
||||
vec = _vec_pad_ones(xs, ys, zs)
|
||||
vecr = np.dot(iM, vec)
|
||||
try:
|
||||
vecr = vecr / vecr[3]
|
||||
except OverflowError:
|
||||
pass
|
||||
return vecr[0], vecr[1], vecr[2]
|
||||
|
||||
|
||||
def _vec_pad_ones(xs, ys, zs):
|
||||
return np.array([xs, ys, zs, np.ones_like(xs)])
|
||||
|
||||
|
||||
def proj_transform(xs, ys, zs, M):
|
||||
"""
|
||||
Transform the points by the projection matrix
|
||||
"""
|
||||
vec = _vec_pad_ones(xs, ys, zs)
|
||||
return _proj_transform_vec(vec, M)
|
||||
|
||||
|
||||
transform = proj_transform
|
||||
|
||||
|
||||
def proj_transform_clip(xs, ys, zs, M):
|
||||
"""
|
||||
Transform the points by the projection matrix
|
||||
and return the clipping result
|
||||
returns txs, tys, tzs, tis
|
||||
"""
|
||||
vec = _vec_pad_ones(xs, ys, zs)
|
||||
return _proj_transform_vec_clip(vec, M)
|
||||
|
||||
|
||||
def proj_points(points, M):
|
||||
return np.column_stack(proj_trans_points(points, M))
|
||||
|
||||
|
||||
def proj_trans_points(points, M):
|
||||
xs, ys, zs = zip(*points)
|
||||
return proj_transform(xs, ys, zs, M)
|
||||
|
||||
|
||||
def rot_x(V, alpha):
|
||||
cosa, sina = np.cos(alpha), np.sin(alpha)
|
||||
M1 = np.array([[1, 0, 0, 0],
|
||||
[0, cosa, -sina, 0],
|
||||
[0, sina, cosa, 0],
|
||||
[0, 0, 0, 1]])
|
||||
return np.dot(M1, V)
|
10
venv/Lib/site-packages/mpl_toolkits/tests/__init__.py
Normal file
10
venv/Lib/site-packages/mpl_toolkits/tests/__init__.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from pathlib import Path
|
||||
|
||||
|
||||
# Check that the test directories exist
|
||||
if not (Path(__file__).parent / "baseline_images").exists():
|
||||
raise IOError(
|
||||
'The baseline image directory does not exist. '
|
||||
'This is most likely because the test data is not installed. '
|
||||
'You may need to install matplotlib from source to get the '
|
||||
'test data.')
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue