demo + utils venv

This commit is contained in:
d3m1g0d
2019-02-03 13:40:10 +01:00
parent 5fa112490b
commit cfa9c8ea23
5994 changed files with 1353819 additions and 0 deletions
@@ -0,0 +1,12 @@
from . import axes_size as Size
from .axes_divider import Divider, SubplotDivider, LocatableAxes, \
make_axes_locatable
from .axes_grid import Grid, ImageGrid, AxesGrid
#from axes_divider import make_axes_locatable
from 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')
@@ -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,5 @@
from mpl_toolkits.axes_grid1.axes_divider import (
AxesDivider, AxesLocator, Divider, SubplotDivider, locatable_axes_factory,
make_axes_locatable)
from mpl_toolkits.axisartist.axes_divider import LocatableAxes
from mpl_toolkits.axisartist.axislines import Axes
@@ -0,0 +1,3 @@
from mpl_toolkits.axisartist.axes_divider import LocatableAxes
from mpl_toolkits.axisartist.axes_grid import (
AxesGrid, CbarAxes, Grid, ImageGrid)
@@ -0,0 +1,9 @@
#from mpl_toolkits.axes_grid1.axes_rgb import *
from mpl_toolkits.axes_grid1.axes_rgb import make_rgb_axes, imshow_rgb, RGBAxesBase
#import mpl_toolkits.axes_grid1.axes_rgb as axes_rgb_orig
from mpl_toolkits.axisartist.axislines import Axes
class RGBAxes(RGBAxesBase):
_defaultAxesClass = Axes
@@ -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,14 @@
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(axes_class=ParasiteAxes)
HostAxes = host_axes_class_factory(axes_class=Axes)
SubplotHost = subplot_class_factory(HostAxes)
@@ -0,0 +1,7 @@
from . import axes_size as Size
from .axes_divider import Divider, SubplotDivider, LocatableAxes, \
make_axes_locatable
from .axes_grid import Grid, ImageGrid, AxesGrid
#from axes_divider import make_axes_locatable
from .parasite_axes import host_subplot, host_axes
@@ -0,0 +1,595 @@
from matplotlib import docstring, transforms
from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox,
DrawingArea, TextArea, VPacker)
from matplotlib.patches import (Rectangle, Ellipse, ArrowStyle,
FancyArrowPatch, PathPatch)
from matplotlib.text import TextPath
__all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox',
'AnchoredEllipse', 'AnchoredSizeBar', 'AnchoredDirectionArrows']
class AnchoredDrawingArea(AnchoredOffsetbox):
@docstring.dedent
def __init__(self, width, height, xdescent, ydescent,
loc, pad=0.4, borderpad=0.5, prop=None, frameon=True,
**kwargs):
"""
An anchored container with a fixed size and fillable DrawingArea.
Artists added to the *drawing_area* will have their coordinates
interpreted as pixels. Any transformations set on the artists will be
overridden.
Parameters
----------
width, height : int or float
width and height of the container, in pixels.
xdescent, ydescent : int or float
descent of the container in the x- and y- direction, in pixels.
loc : int
Location of this artist. Valid location codes are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4,
'right' : 5,
'center left' : 6,
'center right' : 7,
'lower center' : 8,
'upper center' : 9,
'center' : 10
pad : int or float, optional
Padding around the child objects, in fraction of the font
size. Defaults to 0.4.
borderpad : int or float, optional
Border padding, in fraction of the font size.
Defaults to 0.5.
prop : `matplotlib.font_manager.FontProperties`, optional
Font property used as a reference for paddings.
frameon : bool, optional
If True, draw a box around this artists. Defaults to True.
**kwargs :
Keyworded arguments to pass to
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
Attributes
----------
drawing_area : `matplotlib.offsetbox.DrawingArea`
A container for artists to display.
Examples
--------
To display blue and red circles of different sizes in the upper right
of an axes *ax*:
>>> ada = AnchoredDrawingArea(20, 20, 0, 0,
... loc='upper right', frameon=False)
>>> ada.drawing_area.add_artist(Circle((10, 10), 10, fc="b"))
>>> ada.drawing_area.add_artist(Circle((30, 10), 5, fc="r"))
>>> ax.add_artist(ada)
"""
self.da = DrawingArea(width, height, xdescent, ydescent)
self.drawing_area = self.da
super().__init__(
loc, pad=pad, borderpad=borderpad, child=self.da, prop=None,
frameon=frameon, **kwargs
)
class AnchoredAuxTransformBox(AnchoredOffsetbox):
@docstring.dedent
def __init__(self, transform, loc,
pad=0.4, borderpad=0.5, prop=None, frameon=True, **kwargs):
"""
An anchored container with transformed coordinates.
Artists added to the *drawing_area* are scaled according to the
coordinates of the transformation used. The dimensions of this artist
will scale to contain the artists added.
Parameters
----------
transform : `matplotlib.transforms.Transform`
The transformation object for the coordinate system in use, i.e.,
:attr:`matplotlib.axes.Axes.transData`.
loc : int
Location of this artist. Valid location codes are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4,
'right' : 5,
'center left' : 6,
'center right' : 7,
'lower center' : 8,
'upper center' : 9,
'center' : 10
pad : int or float, optional
Padding around the child objects, in fraction of the font
size. Defaults to 0.4.
borderpad : int or float, optional
Border padding, in fraction of the font size.
Defaults to 0.5.
prop : `matplotlib.font_manager.FontProperties`, optional
Font property used as a reference for paddings.
frameon : bool, optional
If True, draw a box around this artists. Defaults to True.
**kwargs :
Keyworded arguments to pass to
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
Attributes
----------
drawing_area : `matplotlib.offsetbox.AuxTransformBox`
A container for artists to display.
Examples
--------
To display an ellipse in the upper left, with a width of 0.1 and
height of 0.4 in data coordinates:
>>> box = AnchoredAuxTransformBox(ax.transData, loc='upper left')
>>> el = Ellipse((0,0), width=0.1, height=0.4, angle=30)
>>> box.drawing_area.add_artist(el)
>>> ax.add_artist(box)
"""
self.drawing_area = AuxTransformBox(transform)
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
child=self.drawing_area,
prop=prop,
frameon=frameon,
**kwargs)
class AnchoredEllipse(AnchoredOffsetbox):
@docstring.dedent
def __init__(self, transform, width, height, angle, loc,
pad=0.1, borderpad=0.1, prop=None, frameon=True, **kwargs):
"""
Draw an anchored ellipse of a given size.
Parameters
----------
transform : `matplotlib.transforms.Transform`
The transformation object for the coordinate system in use, i.e.,
:attr:`matplotlib.axes.Axes.transData`.
width, height : int or float
Width and height of the ellipse, given in coordinates of
*transform*.
angle : int or float
Rotation of the ellipse, in degrees, anti-clockwise.
loc : int
Location of this size bar. Valid location codes are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4,
'right' : 5,
'center left' : 6,
'center right' : 7,
'lower center' : 8,
'upper center' : 9,
'center' : 10
pad : int or float, optional
Padding around the ellipse, in fraction of the font size. Defaults
to 0.1.
borderpad : int or float, optional
Border padding, in fraction of the font size. Defaults to 0.1.
frameon : bool, optional
If True, draw a box around the ellipse. Defaults to True.
prop : `matplotlib.font_manager.FontProperties`, optional
Font property used as a reference for paddings.
**kwargs :
Keyworded arguments to pass to
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
Attributes
----------
ellipse : `matplotlib.patches.Ellipse`
Ellipse patch drawn.
"""
self._box = AuxTransformBox(transform)
self.ellipse = Ellipse((0, 0), width, height, angle)
self._box.add_artist(self.ellipse)
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
child=self._box,
prop=prop,
frameon=frameon, **kwargs)
class AnchoredSizeBar(AnchoredOffsetbox):
@docstring.dedent
def __init__(self, transform, size, label, loc,
pad=0.1, borderpad=0.1, sep=2,
frameon=True, size_vertical=0, color='black',
label_top=False, fontproperties=None, fill_bar=None,
**kwargs):
"""
Draw a horizontal scale bar with a center-aligned label underneath.
Parameters
----------
transform : `matplotlib.transforms.Transform`
The transformation object for the coordinate system in use, i.e.,
:attr:`matplotlib.axes.Axes.transData`.
size : int or float
Horizontal length of the size bar, given in coordinates of
*transform*.
label : str
Label to display.
loc : int
Location of this size bar. Valid location codes are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4,
'right' : 5,
'center left' : 6,
'center right' : 7,
'lower center' : 8,
'upper center' : 9,
'center' : 10
pad : int or float, optional
Padding around the label and size bar, in fraction of the font
size. Defaults to 0.1.
borderpad : int or float, optional
Border padding, in fraction of the font size.
Defaults to 0.1.
sep : int or float, optional
Separation between the label and the size bar, in points.
Defaults to 2.
frameon : bool, optional
If True, draw a box around the horizontal bar and label.
Defaults to True.
size_vertical : int or float, optional
Vertical length of the size bar, given in coordinates of
*transform*. Defaults to 0.
color : str, optional
Color for the size bar and label.
Defaults to black.
label_top : bool, optional
If True, the label will be over the size bar.
Defaults to False.
fontproperties : `matplotlib.font_manager.FontProperties`, optional
Font properties for the label text.
fill_bar : bool, optional
If True and if size_vertical is nonzero, the size bar will
be filled in with the color specified by the size bar.
Defaults to True if `size_vertical` is greater than
zero and False otherwise.
**kwargs :
Keyworded arguments to pass to
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
Attributes
----------
size_bar : `matplotlib.offsetbox.AuxTransformBox`
Container for the size bar.
txt_label : `matplotlib.offsetbox.TextArea`
Container for the label of the size bar.
Notes
-----
If *prop* is passed as a keyworded argument, but *fontproperties* is
not, then *prop* is be assumed to be the intended *fontproperties*.
Using both *prop* and *fontproperties* is not supported.
Examples
--------
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> from mpl_toolkits.axes_grid1.anchored_artists import (
... AnchoredSizeBar)
>>> fig, ax = plt.subplots()
>>> ax.imshow(np.random.random((10,10)))
>>> bar = AnchoredSizeBar(ax.transData, 3, '3 data units', 4)
>>> ax.add_artist(bar)
>>> fig.show()
Using all the optional parameters
>>> import matplotlib.font_manager as fm
>>> fontprops = fm.FontProperties(size=14, family='monospace')
>>> bar = AnchoredSizeBar(ax.transData, 3, '3 units', 4, pad=0.5,
... sep=5, borderpad=0.5, frameon=False,
... size_vertical=0.5, color='white',
... fontproperties=fontprops)
"""
if fill_bar is None:
fill_bar = size_vertical > 0
self.size_bar = AuxTransformBox(transform)
self.size_bar.add_artist(Rectangle((0, 0), size, size_vertical,
fill=fill_bar, facecolor=color,
edgecolor=color))
if fontproperties is None and 'prop' in kwargs:
fontproperties = kwargs.pop('prop')
if fontproperties is None:
textprops = {'color': color}
else:
textprops = {'color': color, 'fontproperties': fontproperties}
self.txt_label = TextArea(
label,
minimumdescent=False,
textprops=textprops)
if label_top:
_box_children = [self.txt_label, self.size_bar]
else:
_box_children = [self.size_bar, self.txt_label]
self._box = VPacker(children=_box_children,
align="center",
pad=0, sep=sep)
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
child=self._box,
prop=fontproperties,
frameon=frameon, **kwargs)
class AnchoredDirectionArrows(AnchoredOffsetbox):
@docstring.dedent
def __init__(self, transform, label_x, label_y, length=0.15,
fontsize=0.08, loc=2, angle=0, aspect_ratio=1, pad=0.4,
borderpad=0.4, frameon=False, color='w', alpha=1,
sep_x=0.01, sep_y=0, fontproperties=None, back_length=0.15,
head_width=10, head_length=15, tail_width=2,
text_props=None, arrow_props=None,
**kwargs):
"""
Draw two perpendicular arrows to indicate directions.
Parameters
----------
transform : `matplotlib.transforms.Transform`
The transformation object for the coordinate system in use, i.e.,
:attr:`matplotlib.axes.Axes.transAxes`.
label_x, label_y : string
Label text for the x and y arrows
length : int or float, optional
Length of the arrow, given in coordinates of
*transform*.
Defaults to 0.15.
fontsize : int, optional
Size of label strings, given in coordinates of *transform*.
Defaults to 0.08.
loc : int, optional
Location of the direction arrows. Valid location codes are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4,
'right' : 5,
'center left' : 6,
'center right' : 7,
'lower center' : 8,
'upper center' : 9,
'center' : 10
Defaults to 2.
angle : int or float, optional
The angle of the arrows in degrees.
Defaults to 0.
aspect_ratio : int or float, optional
The ratio of the length of arrow_x and arrow_y.
Negative numbers can be used to change the direction.
Defaults to 1.
pad : int or float, optional
Padding around the labels and arrows, in fraction of the font
size. Defaults to 0.4.
borderpad : int or float, optional
Border padding, in fraction of the font size.
Defaults to 0.4.
frameon : bool, optional
If True, draw a box around the arrows and labels.
Defaults to False.
color : str, optional
Color for the arrows and labels.
Defaults to white.
alpha : int or float, optional
Alpha values of the arrows and labels
Defaults to 1.
sep_x, sep_y : int or float, optional
Separation between the arrows and labels in coordinates of
*transform*. Defaults to 0.01 and 0.
fontproperties : `matplotlib.font_manager.FontProperties`, optional
Font properties for the label text.
back_length : float, optional
Fraction of the arrow behind the arrow crossing.
Defaults to 0.15.
head_width : int or float, optional
Width of arrow head, sent to ArrowStyle.
Defaults to 10.
head_length : int or float, optional
Length of arrow head, sent to ArrowStyle.
Defaults to 15.
tail_width : int or float, optional
Width of arrow tail, sent to ArrowStyle.
Defaults to 2.
text_props, arrow_props : dict
Properties of the text and arrows, passed to
:class:`matplotlib.text.TextPath` and
`matplotlib.patches.FancyArrowPatch`
**kwargs :
Keyworded arguments to pass to
:class:`matplotlib.offsetbox.AnchoredOffsetbox`.
Attributes
----------
arrow_x, arrow_y : `matplotlib.patches.FancyArrowPatch`
Arrow x and y
text_path_x, text_path_y : `matplotlib.text.TextPath`
Path for arrow labels
p_x, p_y : `matplotlib.patches.PathPatch`
Patch for arrow labels
box : `matplotlib.offsetbox.AuxTransformBox`
Container for the arrows and labels.
Notes
-----
If *prop* is passed as a keyword argument, but *fontproperties* is
not, then *prop* is be assumed to be the intended *fontproperties*.
Using both *prop* and *fontproperties* is not supported.
Examples
--------
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> from mpl_toolkits.axes_grid1.anchored_artists import (
... AnchoredDirectionArrows)
>>> fig, ax = plt.subplots()
>>> ax.imshow(np.random.random((10,10)))
>>> arrows = AnchoredDirectionArrows(ax.transAxes, '111', '110')
>>> ax.add_artist(arrows)
>>> fig.show()
Using several of the optional parameters, creating downward pointing
arrow and high contrast text labels.
>>> import matplotlib.font_manager as fm
>>> fontprops = fm.FontProperties(family='monospace')
>>> arrows = AnchoredDirectionArrows(ax.transAxes, 'East', 'South',
... loc='lower left', color='k',
... aspect_ratio=-1, sep_x=0.02,
... sep_y=-0.01,
... text_props={'ec':'w', 'fc':'k'},
... fontproperties=fontprops)
"""
if arrow_props is None:
arrow_props = {}
if text_props is None:
text_props = {}
arrowstyle = ArrowStyle("Simple",
head_width=head_width,
head_length=head_length,
tail_width=tail_width)
if fontproperties is None and 'prop' in kwargs:
fontproperties = kwargs.pop('prop')
if 'color' not in arrow_props:
arrow_props['color'] = color
if 'alpha' not in arrow_props:
arrow_props['alpha'] = alpha
if 'color' not in text_props:
text_props['color'] = color
if 'alpha' not in text_props:
text_props['alpha'] = alpha
t_start = transform
t_end = t_start + transforms.Affine2D().rotate_deg(angle)
self.box = AuxTransformBox(t_end)
length_x = length
length_y = length*aspect_ratio
self.arrow_x = FancyArrowPatch(
(0, back_length*length_y),
(length_x, back_length*length_y),
arrowstyle=arrowstyle,
shrinkA=0.0,
shrinkB=0.0,
**arrow_props)
self.arrow_y = FancyArrowPatch(
(back_length*length_x, 0),
(back_length*length_x, length_y),
arrowstyle=arrowstyle,
shrinkA=0.0,
shrinkB=0.0,
**arrow_props)
self.box.add_artist(self.arrow_x)
self.box.add_artist(self.arrow_y)
text_path_x = TextPath((
length_x+sep_x, back_length*length_y+sep_y), label_x,
size=fontsize, prop=fontproperties)
self.p_x = PathPatch(text_path_x, transform=t_start, **text_props)
self.box.add_artist(self.p_x)
text_path_y = TextPath((
length_x*back_length+sep_x, length_y*(1-back_length)+sep_y),
label_y, size=fontsize, prop=fontproperties)
self.p_y = PathPatch(text_path_y, **text_props)
self.box.add_artist(self.p_y)
AnchoredOffsetbox.__init__(self, loc, pad=pad, borderpad=borderpad,
child=self.box,
frameon=frameon, **kwargs)
@@ -0,0 +1,917 @@
"""
The axes_divider module provides helper classes to adjust the positions of
multiple axes at drawing time.
Divider: this is the class that is used to calculate the axes
position. It divides the given rectangular area into several sub
rectangles. You initialize the divider by setting the horizontal
and vertical lists of sizes that the division will be based on. You
then use the new_locator method, whose return value is a callable
object that can be used to set the axes_locator of the axes.
"""
import functools
import matplotlib.transforms as mtransforms
from matplotlib import cbook
from matplotlib.axes import SubplotBase
from . import axes_size as Size
class Divider(object):
"""
This class calculates the axes position. It
divides the given rectangular area into several
sub-rectangles. You initialize the divider by setting the
horizontal and vertical lists of sizes
(:mod:`mpl_toolkits.axes_grid.axes_size`) that the division will
be based on. You then use the new_locator method to create a
callable object that can be used as the axes_locator of the
axes.
"""
def __init__(self, fig, pos, horizontal, vertical,
aspect=None, anchor="C"):
"""
Parameters
----------
fig : Figure
pos : tuple of 4 floats
position of the rectangle that will be divided
horizontal : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
sizes for horizontal division
vertical : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
sizes for vertical division
aspect : bool
if True, the overall rectangular area is reduced
so that the relative part of the horizontal and
vertical scales have the same scale.
anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'}
placement of the reduced rectangle when *aspect* is True
"""
self._fig = fig
self._pos = pos
self._horizontal = horizontal
self._vertical = vertical
self._anchor = anchor
self._aspect = aspect
self._xrefindex = 0
self._yrefindex = 0
self._locator = None
def get_horizontal_sizes(self, renderer):
return [s.get_size(renderer) for s in self.get_horizontal()]
def get_vertical_sizes(self, renderer):
return [s.get_size(renderer) for s in self.get_vertical()]
def get_vsize_hsize(self):
from .axes_size import AddList
vsize = AddList(self.get_vertical())
hsize = AddList(self.get_horizontal())
return vsize, hsize
@staticmethod
def _calc_k(l, total_size):
rs_sum, as_sum = 0., 0.
for _rs, _as in l:
rs_sum += _rs
as_sum += _as
if rs_sum != 0.:
k = (total_size - as_sum) / rs_sum
return k
else:
return 0.
@staticmethod
def _calc_offsets(l, k):
offsets = [0.]
#for s in l:
for _rs, _as in l:
#_rs, _as = s.get_size(renderer)
offsets.append(offsets[-1] + _rs*k + _as)
return offsets
def set_position(self, pos):
"""
set the position of the rectangle.
Parameters
----------
pos : tuple of 4 floats
position of the rectangle that will be divided
"""
self._pos = pos
def get_position(self):
"return the position of the rectangle."
return self._pos
def set_anchor(self, anchor):
"""
Parameters
----------
anchor : {'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W'}
anchor position
===== ============
value description
===== ============
'C' Center
'SW' bottom left
'S' bottom
'SE' bottom right
'E' right
'NE' top right
'N' top
'NW' top left
'W' left
===== ============
"""
if anchor in mtransforms.Bbox.coefs or len(anchor) == 2:
self._anchor = anchor
else:
raise ValueError('argument must be among %s' %
', '.join(mtransforms.BBox.coefs))
def get_anchor(self):
"return the anchor"
return self._anchor
def set_horizontal(self, h):
"""
Parameters
----------
h : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
sizes for horizontal division
"""
self._horizontal = h
def get_horizontal(self):
"return horizontal sizes"
return self._horizontal
def set_vertical(self, v):
"""
Parameters
----------
v : list of :mod:`~mpl_toolkits.axes_grid.axes_size`
sizes for vertical division
"""
self._vertical = v
def get_vertical(self):
"return vertical sizes"
return self._vertical
def set_aspect(self, aspect=False):
"""
Parameters
----------
aspect : bool
"""
self._aspect = aspect
def get_aspect(self):
"return aspect"
return self._aspect
def set_locator(self, _locator):
self._locator = _locator
def get_locator(self):
return self._locator
def get_position_runtime(self, ax, renderer):
if self._locator is None:
return self.get_position()
else:
return self._locator(ax, renderer).bounds
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
"""
Parameters
----------
nx, nx1 : int
Integers specifying the column-position of the
cell. When *nx1* is None, a single *nx*-th column is
specified. Otherwise location of columns spanning between *nx*
to *nx1* (but excluding *nx1*-th column) is specified.
ny, ny1 : int
Same as *nx* and *nx1*, but for row positions.
axes
renderer
"""
figW, figH = self._fig.get_size_inches()
x, y, w, h = self.get_position_runtime(axes, renderer)
hsizes = self.get_horizontal_sizes(renderer)
vsizes = self.get_vertical_sizes(renderer)
k_h = self._calc_k(hsizes, figW*w)
k_v = self._calc_k(vsizes, figH*h)
if self.get_aspect():
k = min(k_h, k_v)
ox = self._calc_offsets(hsizes, k)
oy = self._calc_offsets(vsizes, k)
ww = (ox[-1] - ox[0])/figW
hh = (oy[-1] - oy[0])/figH
pb = mtransforms.Bbox.from_bounds(x, y, w, h)
pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
pb1_anchored = pb1.anchored(self.get_anchor(), pb)
x0, y0 = pb1_anchored.x0, pb1_anchored.y0
else:
ox = self._calc_offsets(hsizes, k_h)
oy = self._calc_offsets(vsizes, k_v)
x0, y0 = x, y
if nx1 is None:
nx1 = nx+1
if ny1 is None:
ny1 = ny+1
x1, w1 = x0 + ox[nx]/figW, (ox[nx1] - ox[nx])/figW
y1, h1 = y0 + oy[ny]/figH, (oy[ny1] - oy[ny])/figH
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
def new_locator(self, nx, ny, nx1=None, ny1=None):
"""
Returns a new locator
(:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
specified cell.
Parameters
----------
nx, nx1 : int
Integers specifying the column-position of the
cell. When *nx1* is None, a single *nx*-th column is
specified. Otherwise location of columns spanning between *nx*
to *nx1* (but excluding *nx1*-th column) is specified.
ny, ny1 : int
Same as *nx* and *nx1*, but for row positions.
"""
return AxesLocator(self, nx, ny, nx1, ny1)
def append_size(self, position, size):
if position == "left":
self._horizontal.insert(0, size)
self._xrefindex += 1
elif position == "right":
self._horizontal.append(size)
elif position == "bottom":
self._vertical.insert(0, size)
self._yrefindex += 1
elif position == "top":
self._vertical.append(size)
else:
raise ValueError("the position must be one of left," +
" right, bottom, or top")
def add_auto_adjustable_area(self,
use_axes, pad=0.1,
adjust_dirs=None,
):
if adjust_dirs is None:
adjust_dirs = ["left", "right", "bottom", "top"]
from .axes_size import Padded, SizeFromFunc, GetExtentHelper
for d in adjust_dirs:
helper = GetExtentHelper(use_axes, d)
size = SizeFromFunc(helper)
padded_size = Padded(size, pad) # pad in inch
self.append_size(d, padded_size)
class AxesLocator(object):
"""
A simple callable object, initialized with AxesDivider class,
returns the position and size of the given cell.
"""
def __init__(self, axes_divider, nx, ny, nx1=None, ny1=None):
"""
Parameters
----------
axes_divider : AxesDivider
nx, nx1 : int
Integers specifying the column-position of the
cell. When *nx1* is None, a single *nx*-th column is
specified. Otherwise location of columns spanning between *nx*
to *nx1* (but excluding *nx1*-th column) is specified.
ny, ny1 : int
Same as *nx* and *nx1*, but for row positions.
"""
self._axes_divider = axes_divider
_xrefindex = axes_divider._xrefindex
_yrefindex = axes_divider._yrefindex
self._nx, self._ny = nx - _xrefindex, ny - _yrefindex
if nx1 is None:
nx1 = nx+1
if ny1 is None:
ny1 = ny+1
self._nx1 = nx1 - _xrefindex
self._ny1 = ny1 - _yrefindex
def __call__(self, axes, renderer):
_xrefindex = self._axes_divider._xrefindex
_yrefindex = self._axes_divider._yrefindex
return self._axes_divider.locate(self._nx + _xrefindex,
self._ny + _yrefindex,
self._nx1 + _xrefindex,
self._ny1 + _yrefindex,
axes,
renderer)
def get_subplotspec(self):
if hasattr(self._axes_divider, "get_subplotspec"):
return self._axes_divider.get_subplotspec()
else:
return None
from matplotlib.gridspec import SubplotSpec, GridSpec
class SubplotDivider(Divider):
"""
The Divider class whose rectangle area is specified as a subplot geometry.
"""
def __init__(self, fig, *args, horizontal=None, vertical=None,
aspect=None, anchor='C'):
"""
Parameters
----------
fig : :class:`matplotlib.figure.Figure`
args : tuple (*numRows*, *numCols*, *plotNum*)
The array of subplots in the figure has dimensions *numRows*,
*numCols*, and *plotNum* is the number of the subplot
being created. *plotNum* starts at 1 in the upper left
corner and increases to the right.
If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the
decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*.
"""
self.figure = fig
if len(args) == 1:
if isinstance(args[0], SubplotSpec):
self._subplotspec = args[0]
else:
try:
s = str(int(args[0]))
rows, cols, num = map(int, s)
except ValueError:
raise ValueError(
'Single argument to subplot must be a 3-digit integer')
self._subplotspec = GridSpec(rows, cols)[num-1]
# num - 1 for converting from MATLAB to python indexing
elif len(args) == 3:
rows, cols, num = args
rows = int(rows)
cols = int(cols)
if isinstance(num, tuple) and len(num) == 2:
num = [int(n) for n in num]
self._subplotspec = GridSpec(rows, cols)[num[0]-1:num[1]]
else:
self._subplotspec = GridSpec(rows, cols)[int(num)-1]
# num - 1 for converting from MATLAB to python indexing
else:
raise ValueError('Illegal argument(s) to subplot: %s' % (args,))
# total = rows*cols
# num -= 1 # convert from matlab to python indexing
# # i.e., num in range(0,total)
# if num >= total:
# raise ValueError( 'Subplot number exceeds total subplots')
# self._rows = rows
# self._cols = cols
# self._num = num
# self.update_params()
# sets self.fixbox
self.update_params()
pos = self.figbox.bounds
Divider.__init__(self, fig, pos, horizontal or [], vertical or [],
aspect=aspect, anchor=anchor)
def get_position(self):
"return the bounds of the subplot box"
self.update_params() # update self.figbox
return self.figbox.bounds
# def update_params(self):
# 'update the subplot position from fig.subplotpars'
# rows = self._rows
# cols = self._cols
# num = self._num
# pars = self.figure.subplotpars
# left = pars.left
# right = pars.right
# bottom = pars.bottom
# top = pars.top
# wspace = pars.wspace
# hspace = pars.hspace
# totWidth = right-left
# totHeight = top-bottom
# figH = totHeight/(rows + hspace*(rows-1))
# sepH = hspace*figH
# figW = totWidth/(cols + wspace*(cols-1))
# sepW = wspace*figW
# rowNum, colNum = divmod(num, cols)
# figBottom = top - (rowNum+1)*figH - rowNum*sepH
# figLeft = left + colNum*(figW + sepW)
# self.figbox = mtransforms.Bbox.from_bounds(figLeft, figBottom,
# figW, figH)
def update_params(self):
'update the subplot position from fig.subplotpars'
self.figbox = self.get_subplotspec().get_position(self.figure)
def get_geometry(self):
'get the subplot geometry, e.g., 2,2,3'
rows, cols, num1, num2 = self.get_subplotspec().get_geometry()
return rows, cols, num1+1 # for compatibility
# COVERAGE NOTE: Never used internally or from examples
def change_geometry(self, numrows, numcols, num):
'change subplot geometry, e.g., from 1,1,1 to 2,2,3'
self._subplotspec = GridSpec(numrows, numcols)[num-1]
self.update_params()
self.set_position(self.figbox)
def get_subplotspec(self):
'get the SubplotSpec instance'
return self._subplotspec
def set_subplotspec(self, subplotspec):
'set the SubplotSpec instance'
self._subplotspec = subplotspec
class AxesDivider(Divider):
"""
Divider based on the pre-existing axes.
"""
def __init__(self, axes, xref=None, yref=None):
"""
Parameters
----------
axes : :class:`~matplotlib.axes.Axes`
xref
yref
"""
self._axes = axes
if xref is None:
self._xref = Size.AxesX(axes)
else:
self._xref = xref
if yref is None:
self._yref = Size.AxesY(axes)
else:
self._yref = yref
Divider.__init__(self, fig=axes.get_figure(), pos=None,
horizontal=[self._xref], vertical=[self._yref],
aspect=None, anchor="C")
def _get_new_axes(self, *, axes_class=None, **kwargs):
axes = self._axes
if axes_class is None:
if isinstance(axes, SubplotBase):
axes_class = axes._axes_class
else:
axes_class = type(axes)
return axes_class(axes.get_figure(), axes.get_position(original=True),
**kwargs)
def new_horizontal(self, size, pad=None, pack_start=False, **kwargs):
"""
Add a new axes on the right (or left) side of the main axes.
Parameters
----------
size : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string
A width of the axes. If float or string is given, *from_any*
function is used to create the size, with *ref_size* set to AxesX
instance of the current axes.
pad : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string
Pad between the axes. It takes same argument as *size*.
pack_start : bool
If False, the new axes is appended at the end
of the list, i.e., it became the right-most axes. If True, it is
inserted at the start of the list, and becomes the left-most axes.
kwargs
All extra keywords arguments are passed to the created axes.
If *axes_class* is given, the new axes will be created as an
instance of the given class. Otherwise, the same class of the
main axes will be used.
"""
if pad:
if not isinstance(pad, Size._Base):
pad = Size.from_any(pad,
fraction_ref=self._xref)
if pack_start:
self._horizontal.insert(0, pad)
self._xrefindex += 1
else:
self._horizontal.append(pad)
if not isinstance(size, Size._Base):
size = Size.from_any(size,
fraction_ref=self._xref)
if pack_start:
self._horizontal.insert(0, size)
self._xrefindex += 1
locator = self.new_locator(nx=0, ny=self._yrefindex)
else:
self._horizontal.append(size)
locator = self.new_locator(nx=len(self._horizontal)-1, ny=self._yrefindex)
ax = self._get_new_axes(**kwargs)
ax.set_axes_locator(locator)
return ax
def new_vertical(self, size, pad=None, pack_start=False, **kwargs):
"""
Add a new axes on the top (or bottom) side of the main axes.
Parameters
----------
size : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string
A height of the axes. If float or string is given, *from_any*
function is used to create the size, with *ref_size* set to AxesX
instance of the current axes.
pad : :mod:`~mpl_toolkits.axes_grid.axes_size` or float or string
Pad between the axes. It takes same argument as *size*.
pack_start : bool
If False, the new axes is appended at the end
of the list, i.e., it became the right-most axes. If True, it is
inserted at the start of the list, and becomes the left-most axes.
kwargs
All extra keywords arguments are passed to the created axes.
If *axes_class* is given, the new axes will be created as an
instance of the given class. Otherwise, the same class of the
main axes will be used.
"""
if pad:
if not isinstance(pad, Size._Base):
pad = Size.from_any(pad,
fraction_ref=self._yref)
if pack_start:
self._vertical.insert(0, pad)
self._yrefindex += 1
else:
self._vertical.append(pad)
if not isinstance(size, Size._Base):
size = Size.from_any(size,
fraction_ref=self._yref)
if pack_start:
self._vertical.insert(0, size)
self._yrefindex += 1
locator = self.new_locator(nx=self._xrefindex, ny=0)
else:
self._vertical.append(size)
locator = self.new_locator(nx=self._xrefindex, ny=len(self._vertical)-1)
ax = self._get_new_axes(**kwargs)
ax.set_axes_locator(locator)
return ax
def append_axes(self, position, size, pad=None, add_to_figure=True,
**kwargs):
"""
create an axes at the given *position* with the same height
(or width) of the main axes.
*position*
["left"|"right"|"bottom"|"top"]
*size* and *pad* should be axes_grid.axes_size compatible.
"""
if position == "left":
ax = self.new_horizontal(size, pad, pack_start=True, **kwargs)
elif position == "right":
ax = self.new_horizontal(size, pad, pack_start=False, **kwargs)
elif position == "bottom":
ax = self.new_vertical(size, pad, pack_start=True, **kwargs)
elif position == "top":
ax = self.new_vertical(size, pad, pack_start=False, **kwargs)
else:
raise ValueError("the position must be one of left," +
" right, bottom, or top")
if add_to_figure:
self._fig.add_axes(ax)
return ax
def get_aspect(self):
if self._aspect is None:
aspect = self._axes.get_aspect()
if aspect == "auto":
return False
else:
return True
else:
return self._aspect
def get_position(self):
if self._pos is None:
bbox = self._axes.get_position(original=True)
return bbox.bounds
else:
return self._pos
def get_anchor(self):
if self._anchor is None:
return self._axes.get_anchor()
else:
return self._anchor
def get_subplotspec(self):
if hasattr(self._axes, "get_subplotspec"):
return self._axes.get_subplotspec()
else:
return None
class HBoxDivider(SubplotDivider):
def __init__(self, fig, *args, **kwargs):
SubplotDivider.__init__(self, fig, *args, **kwargs)
@staticmethod
def _determine_karray(equivalent_sizes, appended_sizes,
max_equivalent_size,
total_appended_size):
n = len(equivalent_sizes)
import numpy as np
A = np.mat(np.zeros((n+1, n+1), dtype="d"))
B = np.zeros((n+1), dtype="d")
# AxK = B
# populated A
for i, (r, a) in enumerate(equivalent_sizes):
A[i, i] = r
A[i, -1] = -1
B[i] = -a
A[-1, :-1] = [r for r, a in appended_sizes]
B[-1] = total_appended_size - sum([a for rs, a in appended_sizes])
karray_H = (A.I*np.mat(B).T).A1
karray = karray_H[:-1]
H = karray_H[-1]
if H > max_equivalent_size:
karray = ((max_equivalent_size -
np.array([a for r, a in equivalent_sizes]))
/ np.array([r for r, a in equivalent_sizes]))
return karray
@staticmethod
def _calc_offsets(appended_sizes, karray):
offsets = [0.]
#for s in l:
for (r, a), k in zip(appended_sizes, karray):
offsets.append(offsets[-1] + r*k + a)
return offsets
def new_locator(self, nx, nx1=None):
"""
returns a new locator
(:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
specified cell.
Parameters
----------
nx, nx1 : int
Integers specifying the column-position of the
cell. When *nx1* is None, a single *nx*-th column is
specified. Otherwise location of columns spanning between *nx*
to *nx1* (but excluding *nx1*-th column) is specified.
ny, ny1 : int
Same as *nx* and *nx1*, but for row positions.
"""
return AxesLocator(self, nx, 0, nx1, None)
def _locate(self, x, y, w, h,
y_equivalent_sizes, x_appended_sizes,
figW, figH):
"""
Parameters
----------
x
y
w
h
y_equivalent_sizes
x_appended_sizes
figW
figH
"""
equivalent_sizes = y_equivalent_sizes
appended_sizes = x_appended_sizes
max_equivalent_size = figH*h
total_appended_size = figW*w
karray = self._determine_karray(equivalent_sizes, appended_sizes,
max_equivalent_size,
total_appended_size)
ox = self._calc_offsets(appended_sizes, karray)
ww = (ox[-1] - ox[0])/figW
ref_h = equivalent_sizes[0]
hh = (karray[0]*ref_h[0] + ref_h[1])/figH
pb = mtransforms.Bbox.from_bounds(x, y, w, h)
pb1 = mtransforms.Bbox.from_bounds(x, y, ww, hh)
pb1_anchored = pb1.anchored(self.get_anchor(), pb)
x0, y0 = pb1_anchored.x0, pb1_anchored.y0
return x0, y0, ox, hh
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
"""
Parameters
----------
axes_divider : AxesDivider
nx, nx1 : int
Integers specifying the column-position of the
cell. When *nx1* is None, a single *nx*-th column is
specified. Otherwise location of columns spanning between *nx*
to *nx1* (but excluding *nx1*-th column) is specified.
ny, ny1 : int
Same as *nx* and *nx1*, but for row positions.
axes
renderer
"""
figW, figH = self._fig.get_size_inches()
x, y, w, h = self.get_position_runtime(axes, renderer)
y_equivalent_sizes = self.get_vertical_sizes(renderer)
x_appended_sizes = self.get_horizontal_sizes(renderer)
x0, y0, ox, hh = self._locate(x, y, w, h,
y_equivalent_sizes, x_appended_sizes,
figW, figH)
if nx1 is None:
nx1 = nx+1
x1, w1 = x0 + ox[nx]/figW, (ox[nx1] - ox[nx])/figW
y1, h1 = y0, hh
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
class VBoxDivider(HBoxDivider):
"""
The Divider class whose rectangle area is specified as a subplot geometry.
"""
def new_locator(self, ny, ny1=None):
"""
returns a new locator
(:class:`mpl_toolkits.axes_grid.axes_divider.AxesLocator`) for
specified cell.
Parameters
----------
ny, ny1 : int
Integers specifying the row-position of the
cell. When *ny1* is None, a single *ny*-th row is
specified. Otherwise location of rows spanning between *ny*
to *ny1* (but excluding *ny1*-th row) is specified.
"""
return AxesLocator(self, 0, ny, None, ny1)
def locate(self, nx, ny, nx1=None, ny1=None, axes=None, renderer=None):
"""
Parameters
----------
axes_divider : AxesDivider
nx, nx1 : int
Integers specifying the column-position of the
cell. When *nx1* is None, a single *nx*-th column is
specified. Otherwise location of columns spanning between *nx*
to *nx1* (but excluding *nx1*-th column) is specified.
ny, ny1 : int
Same as *nx* and *nx1*, but for row positions.
axes
renderer
"""
figW, figH = self._fig.get_size_inches()
x, y, w, h = self.get_position_runtime(axes, renderer)
x_equivalent_sizes = self.get_horizontal_sizes(renderer)
y_appended_sizes = self.get_vertical_sizes(renderer)
y0, x0, oy, ww = self._locate(y, x, h, w,
x_equivalent_sizes, y_appended_sizes,
figH, figW)
if ny1 is None:
ny1 = ny+1
x1, w1 = x0, ww
y1, h1 = y0 + oy[ny]/figH, (oy[ny1] - oy[ny])/figH
return mtransforms.Bbox.from_bounds(x1, y1, w1, h1)
@cbook.deprecated('3.0',
addendum=' There is no alternative. Deriving from '
'matplotlib.axes.Axes provides this functionality '
'already.')
class LocatableAxesBase(object):
pass
@cbook.deprecated('3.0',
addendum=' There is no alternative. Classes derived from '
'matplotlib.axes.Axes provide this functionality '
'already.')
def locatable_axes_factory(axes_class):
return axes_class
def make_axes_locatable(axes):
divider = AxesDivider(axes)
locator = divider.new_locator(nx=0, ny=0)
axes.set_axes_locator(locator)
return divider
def make_axes_area_auto_adjustable(ax,
use_axes=None, pad=0.1,
adjust_dirs=None):
if adjust_dirs is None:
adjust_dirs = ["left", "right", "bottom", "top"]
divider = make_axes_locatable(ax)
if use_axes is None:
use_axes = ax
divider.add_auto_adjustable_area(use_axes=use_axes, pad=pad,
adjust_dirs=adjust_dirs)
from .mpl_axes import Axes as _Axes
@cbook.deprecated('3.0',
alternative='mpl_toolkits.axes_grid1.mpl_axes.Axes')
class Axes(_Axes):
pass
@cbook.deprecated('3.0',
alternative='mpl_toolkits.axes_grid1.mpl_axes.Axes')
class LocatableAxes(_Axes):
pass
@@ -0,0 +1,761 @@
from numbers import Number
import matplotlib.axes as maxes
import matplotlib.ticker as ticker
from matplotlib.gridspec import SubplotSpec
from .axes_divider import Size, SubplotDivider, Divider
from .colorbar import Colorbar
from .mpl_axes import Axes
def _extend_axes_pad(value):
# Check whether a list/tuple/array or scalar has been passed
ret = value
if not hasattr(ret, "__getitem__"):
ret = (value, value)
return ret
def _tick_only(ax, bottom_on, left_on):
bottom_off = not bottom_on
left_off = not left_on
# [l.set_visible(bottom_off) for l in ax.get_xticklabels()]
# [l.set_visible(left_off) for l in ax.get_yticklabels()]
# ax.xaxis.label.set_visible(bottom_off)
# ax.yaxis.label.set_visible(left_off)
ax.axis["bottom"].toggle(ticklabels=bottom_off, label=bottom_off)
ax.axis["left"].toggle(ticklabels=left_off, label=left_off)
class CbarAxesBase(object):
def colorbar(self, mappable, *, locator=None, **kwargs):
if locator is None:
if "ticks" not in kwargs:
kwargs["ticks"] = ticker.MaxNLocator(5)
if locator is not None:
if "ticks" in kwargs:
raise ValueError("Either *locator* or *ticks* need" +
" to be given, not both")
else:
kwargs["ticks"] = locator
if self.orientation in ["top", "bottom"]:
orientation = "horizontal"
else:
orientation = "vertical"
cb = Colorbar(self, mappable, orientation=orientation, **kwargs)
self._config_axes()
def on_changed(m):
cb.set_cmap(m.get_cmap())
cb.set_clim(m.get_clim())
cb.update_bruteforce(m)
self.cbid = mappable.callbacksSM.connect('changed', on_changed)
mappable.colorbar = cb
self.locator = cb.cbar_axis.get_major_locator()
return cb
def _config_axes(self):
'''
Make an axes patch and outline.
'''
ax = self
ax.set_navigate(False)
ax.axis[:].toggle(all=False)
b = self._default_label_on
ax.axis[self.orientation].toggle(all=b)
# for axis in ax.axis.values():
# axis.major_ticks.set_visible(False)
# axis.minor_ticks.set_visible(False)
# axis.major_ticklabels.set_visible(False)
# axis.minor_ticklabels.set_visible(False)
# axis.label.set_visible(False)
# axis = ax.axis[self.orientation]
# axis.major_ticks.set_visible(True)
# axis.minor_ticks.set_visible(True)
#axis.major_ticklabels.set_size(
# int(axis.major_ticklabels.get_size()*.9))
#axis.major_tick_pad = 3
# axis.major_ticklabels.set_visible(b)
# axis.minor_ticklabels.set_visible(b)
# axis.label.set_visible(b)
def toggle_label(self, b):
self._default_label_on = b
axis = self.axis[self.orientation]
axis.toggle(ticklabels=b, label=b)
#axis.major_ticklabels.set_visible(b)
#axis.minor_ticklabels.set_visible(b)
#axis.label.set_visible(b)
class CbarAxes(CbarAxesBase, Axes):
def __init__(self, *args, orientation, **kwargs):
self.orientation = orientation
self._default_label_on = True
self.locator = None
super().__init__(*args, **kwargs)
def cla(self):
super().cla()
self._config_axes()
class Grid(object):
"""
A class that creates a grid of Axes. In matplotlib, the axes
location (and size) is specified in the normalized figure
coordinates. This may not be ideal for images that needs to be
displayed with a given aspect ratio. For example, displaying
images of a same size with some fixed padding between them cannot
be easily done in matplotlib. AxesGrid is used in such case.
"""
_defaultAxesClass = Axes
def __init__(self, fig,
rect,
nrows_ncols,
ngrids=None,
direction="row",
axes_pad=0.02,
add_all=True,
share_all=False,
share_x=True,
share_y=True,
#aspect=True,
label_mode="L",
axes_class=None,
):
"""
Build an :class:`Grid` instance with a grid nrows*ncols
:class:`~matplotlib.axes.Axes` in
:class:`~matplotlib.figure.Figure` *fig* with
*rect=[left, bottom, width, height]* (in
:class:`~matplotlib.figure.Figure` coordinates) or
the subplot position code (e.g., "121").
Optional keyword arguments:
================ ======== =========================================
Keyword Default Description
================ ======== =========================================
direction "row" [ "row" | "column" ]
axes_pad 0.02 float| pad between axes given in inches
or tuple-like of floats,
(horizontal padding, vertical padding)
add_all True bool
share_all False bool
share_x True bool
share_y True bool
label_mode "L" [ "L" | "1" | "all" ]
axes_class None a type object which must be a subclass
of :class:`~matplotlib.axes.Axes`
================ ======== =========================================
"""
self._nrows, self._ncols = nrows_ncols
if ngrids is None:
ngrids = self._nrows * self._ncols
else:
if not 0 < ngrids <= self._nrows * self._ncols:
raise Exception("")
self.ngrids = ngrids
self._init_axes_pad(axes_pad)
if direction not in ["column", "row"]:
raise Exception("")
self._direction = direction
if axes_class is None:
axes_class = self._defaultAxesClass
axes_class_args = {}
else:
if (isinstance(axes_class, type)
and issubclass(axes_class,
self._defaultAxesClass.Axes)):
axes_class_args = {}
else:
axes_class, axes_class_args = axes_class
self.axes_all = []
self.axes_column = [[] for _ in range(self._ncols)]
self.axes_row = [[] for _ in range(self._nrows)]
h = []
v = []
if isinstance(rect, (str, Number)):
self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v,
aspect=False)
elif isinstance(rect, SubplotSpec):
self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v,
aspect=False)
elif len(rect) == 3:
kw = dict(horizontal=h, vertical=v, aspect=False)
self._divider = SubplotDivider(fig, *rect, **kw)
elif len(rect) == 4:
self._divider = Divider(fig, rect, horizontal=h, vertical=v,
aspect=False)
else:
raise Exception("")
rect = self._divider.get_position()
# reference axes
self._column_refax = [None for _ in range(self._ncols)]
self._row_refax = [None for _ in range(self._nrows)]
self._refax = None
for i in range(self.ngrids):
col, row = self._get_col_row(i)
if share_all:
sharex = self._refax
sharey = self._refax
else:
if share_x:
sharex = self._column_refax[col]
else:
sharex = None
if share_y:
sharey = self._row_refax[row]
else:
sharey = None
ax = axes_class(fig, rect, sharex=sharex, sharey=sharey,
**axes_class_args)
if share_all:
if self._refax is None:
self._refax = ax
else:
if sharex is None:
self._column_refax[col] = ax
if sharey is None:
self._row_refax[row] = ax
self.axes_all.append(ax)
self.axes_column[col].append(ax)
self.axes_row[row].append(ax)
self.axes_llc = self.axes_column[0][-1]
self._update_locators()
if add_all:
for ax in self.axes_all:
fig.add_axes(ax)
self.set_label_mode(label_mode)
def _init_axes_pad(self, axes_pad):
axes_pad = _extend_axes_pad(axes_pad)
self._axes_pad = axes_pad
self._horiz_pad_size = Size.Fixed(axes_pad[0])
self._vert_pad_size = Size.Fixed(axes_pad[1])
def _update_locators(self):
h = []
h_ax_pos = []
for _ in self._column_refax:
#if h: h.append(Size.Fixed(self._axes_pad))
if h:
h.append(self._horiz_pad_size)
h_ax_pos.append(len(h))
sz = Size.Scaled(1)
h.append(sz)
v = []
v_ax_pos = []
for _ in self._row_refax[::-1]:
#if v: v.append(Size.Fixed(self._axes_pad))
if v:
v.append(self._vert_pad_size)
v_ax_pos.append(len(v))
sz = Size.Scaled(1)
v.append(sz)
for i in range(self.ngrids):
col, row = self._get_col_row(i)
locator = self._divider.new_locator(nx=h_ax_pos[col],
ny=v_ax_pos[self._nrows - 1 - row])
self.axes_all[i].set_axes_locator(locator)
self._divider.set_horizontal(h)
self._divider.set_vertical(v)
def _get_col_row(self, n):
if self._direction == "column":
col, row = divmod(n, self._nrows)
else:
row, col = divmod(n, self._ncols)
return col, row
# Good to propagate __len__ if we have __getitem__
def __len__(self):
return len(self.axes_all)
def __getitem__(self, i):
return self.axes_all[i]
def get_geometry(self):
"""
get geometry of the grid. Returns a tuple of two integer,
representing number of rows and number of columns.
"""
return self._nrows, self._ncols
def set_axes_pad(self, axes_pad):
"set axes_pad"
self._axes_pad = axes_pad
# These two lines actually differ from ones in _init_axes_pad
self._horiz_pad_size.fixed_size = axes_pad[0]
self._vert_pad_size.fixed_size = axes_pad[1]
def get_axes_pad(self):
"""
get axes_pad
Returns
-------
tuple
Padding in inches, (horizontal pad, vertical pad)
"""
return self._axes_pad
def set_aspect(self, aspect):
"set aspect"
self._divider.set_aspect(aspect)
def get_aspect(self):
"get aspect"
return self._divider.get_aspect()
def set_label_mode(self, mode):
"set label_mode"
if mode == "all":
for ax in self.axes_all:
_tick_only(ax, False, False)
elif mode == "L":
# left-most axes
for ax in self.axes_column[0][:-1]:
_tick_only(ax, bottom_on=True, left_on=False)
# lower-left axes
ax = self.axes_column[0][-1]
_tick_only(ax, bottom_on=False, left_on=False)
for col in self.axes_column[1:]:
# axes with no labels
for ax in col[:-1]:
_tick_only(ax, bottom_on=True, left_on=True)
# bottom
ax = col[-1]
_tick_only(ax, bottom_on=False, left_on=True)
elif mode == "1":
for ax in self.axes_all:
_tick_only(ax, bottom_on=True, left_on=True)
ax = self.axes_llc
_tick_only(ax, bottom_on=False, left_on=False)
def get_divider(self):
return self._divider
def set_axes_locator(self, locator):
self._divider.set_locator(locator)
def get_axes_locator(self):
return self._divider.get_locator()
def get_vsize_hsize(self):
return self._divider.get_vsize_hsize()
# from axes_size import AddList
# vsize = AddList(self._divider.get_vertical())
# hsize = AddList(self._divider.get_horizontal())
# return vsize, hsize
class ImageGrid(Grid):
"""
A class that creates a grid of Axes. In matplotlib, the axes
location (and size) is specified in the normalized figure
coordinates. This may not be ideal for images that needs to be
displayed with a given aspect ratio. For example, displaying
images of a same size with some fixed padding between them cannot
be easily done in matplotlib. ImageGrid is used in such case.
"""
_defaultCbarAxesClass = CbarAxes
def __init__(self, fig,
rect,
nrows_ncols,
ngrids=None,
direction="row",
axes_pad=0.02,
add_all=True,
share_all=False,
aspect=True,
label_mode="L",
cbar_mode=None,
cbar_location="right",
cbar_pad=None,
cbar_size="5%",
cbar_set_cax=True,
axes_class=None,
):
"""
Build an :class:`ImageGrid` instance with a grid nrows*ncols
:class:`~matplotlib.axes.Axes` in
:class:`~matplotlib.figure.Figure` *fig* with
*rect=[left, bottom, width, height]* (in
:class:`~matplotlib.figure.Figure` coordinates) or
the subplot position code (e.g., "121").
Optional keyword arguments:
================ ======== =========================================
Keyword Default Description
================ ======== =========================================
direction "row" [ "row" | "column" ]
axes_pad 0.02 float| pad between axes given in inches
or tuple-like of floats,
(horizontal padding, vertical padding)
add_all True bool
share_all False bool
aspect True bool
label_mode "L" [ "L" | "1" | "all" ]
cbar_mode None [ "each" | "single" | "edge" ]
cbar_location "right" [ "left" | "right" | "bottom" | "top" ]
cbar_pad None
cbar_size "5%"
cbar_set_cax True bool
axes_class None a type object which must be a subclass
of axes_grid's subclass of
:class:`~matplotlib.axes.Axes`
================ ======== =========================================
*cbar_set_cax* : if True, each axes in the grid has a cax
attribute that is bind to associated cbar_axes.
"""
self._nrows, self._ncols = nrows_ncols
if ngrids is None:
ngrids = self._nrows * self._ncols
else:
if not 0 <= ngrids < self._nrows * self._ncols:
raise Exception
self.ngrids = ngrids
axes_pad = _extend_axes_pad(axes_pad)
self._axes_pad = axes_pad
self._colorbar_mode = cbar_mode
self._colorbar_location = cbar_location
if cbar_pad is None:
# horizontal or vertical arrangement?
if cbar_location in ("left", "right"):
self._colorbar_pad = axes_pad[0]
else:
self._colorbar_pad = axes_pad[1]
else:
self._colorbar_pad = cbar_pad
self._colorbar_size = cbar_size
self._init_axes_pad(axes_pad)
if direction not in ["column", "row"]:
raise Exception("")
self._direction = direction
if axes_class is None:
axes_class = self._defaultAxesClass
axes_class_args = {}
else:
if isinstance(axes_class, maxes.Axes):
axes_class_args = {}
else:
axes_class, axes_class_args = axes_class
self.axes_all = []
self.axes_column = [[] for _ in range(self._ncols)]
self.axes_row = [[] for _ in range(self._nrows)]
self.cbar_axes = []
h = []
v = []
if isinstance(rect, (str, Number)):
self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v,
aspect=aspect)
elif isinstance(rect, SubplotSpec):
self._divider = SubplotDivider(fig, rect, horizontal=h, vertical=v,
aspect=aspect)
elif len(rect) == 3:
kw = dict(horizontal=h, vertical=v, aspect=aspect)
self._divider = SubplotDivider(fig, *rect, **kw)
elif len(rect) == 4:
self._divider = Divider(fig, rect, horizontal=h, vertical=v,
aspect=aspect)
else:
raise Exception("")
rect = self._divider.get_position()
# reference axes
self._column_refax = [None for _ in range(self._ncols)]
self._row_refax = [None for _ in range(self._nrows)]
self._refax = None
for i in range(self.ngrids):
col, row = self._get_col_row(i)
if share_all:
if self.axes_all:
sharex = self.axes_all[0]
sharey = self.axes_all[0]
else:
sharex = None
sharey = None
else:
sharex = self._column_refax[col]
sharey = self._row_refax[row]
ax = axes_class(fig, rect, sharex=sharex, sharey=sharey,
**axes_class_args)
self.axes_all.append(ax)
self.axes_column[col].append(ax)
self.axes_row[row].append(ax)
if share_all:
if self._refax is None:
self._refax = ax
if sharex is None:
self._column_refax[col] = ax
if sharey is None:
self._row_refax[row] = ax
cax = self._defaultCbarAxesClass(fig, rect,
orientation=self._colorbar_location)
self.cbar_axes.append(cax)
self.axes_llc = self.axes_column[0][-1]
self._update_locators()
if add_all:
for ax in self.axes_all+self.cbar_axes:
fig.add_axes(ax)
if cbar_set_cax:
if self._colorbar_mode == "single":
for ax in self.axes_all:
ax.cax = self.cbar_axes[0]
elif self._colorbar_mode == "edge":
for index, ax in enumerate(self.axes_all):
col, row = self._get_col_row(index)
if self._colorbar_location in ("left", "right"):
ax.cax = self.cbar_axes[row]
else:
ax.cax = self.cbar_axes[col]
else:
for ax, cax in zip(self.axes_all, self.cbar_axes):
ax.cax = cax
self.set_label_mode(label_mode)
def _update_locators(self):
h = []
v = []
h_ax_pos = []
h_cb_pos = []
if (self._colorbar_mode == "single" and
self._colorbar_location in ('left', 'bottom')):
if self._colorbar_location == "left":
#sz = Size.Fraction(Size.AxesX(self.axes_llc), self._nrows)
sz = Size.Fraction(self._nrows, Size.AxesX(self.axes_llc))
h.append(Size.from_any(self._colorbar_size, sz))
h.append(Size.from_any(self._colorbar_pad, sz))
locator = self._divider.new_locator(nx=0, ny=0, ny1=-1)
elif self._colorbar_location == "bottom":
#sz = Size.Fraction(Size.AxesY(self.axes_llc), self._ncols)
sz = Size.Fraction(self._ncols, Size.AxesY(self.axes_llc))
v.append(Size.from_any(self._colorbar_size, sz))
v.append(Size.from_any(self._colorbar_pad, sz))
locator = self._divider.new_locator(nx=0, nx1=-1, ny=0)
for i in range(self.ngrids):
self.cbar_axes[i].set_visible(False)
self.cbar_axes[0].set_axes_locator(locator)
self.cbar_axes[0].set_visible(True)
for col, ax in enumerate(self.axes_row[0]):
if h:
h.append(self._horiz_pad_size) # Size.Fixed(self._axes_pad))
if ax:
sz = Size.AxesX(ax, aspect="axes", ref_ax=self.axes_all[0])
else:
sz = Size.AxesX(self.axes_all[0],
aspect="axes", ref_ax=self.axes_all[0])
if (self._colorbar_mode == "each" or
(self._colorbar_mode == 'edge' and
col == 0)) and self._colorbar_location == "left":
h_cb_pos.append(len(h))
h.append(Size.from_any(self._colorbar_size, sz))
h.append(Size.from_any(self._colorbar_pad, sz))
h_ax_pos.append(len(h))
h.append(sz)
if ((self._colorbar_mode == "each" or
(self._colorbar_mode == 'edge' and
col == self._ncols - 1)) and
self._colorbar_location == "right"):
h.append(Size.from_any(self._colorbar_pad, sz))
h_cb_pos.append(len(h))
h.append(Size.from_any(self._colorbar_size, sz))
v_ax_pos = []
v_cb_pos = []
for row, ax in enumerate(self.axes_column[0][::-1]):
if v:
v.append(self._vert_pad_size) # Size.Fixed(self._axes_pad))
if ax:
sz = Size.AxesY(ax, aspect="axes", ref_ax=self.axes_all[0])
else:
sz = Size.AxesY(self.axes_all[0],
aspect="axes", ref_ax=self.axes_all[0])
if (self._colorbar_mode == "each" or
(self._colorbar_mode == 'edge' and
row == 0)) and self._colorbar_location == "bottom":
v_cb_pos.append(len(v))
v.append(Size.from_any(self._colorbar_size, sz))
v.append(Size.from_any(self._colorbar_pad, sz))
v_ax_pos.append(len(v))
v.append(sz)
if ((self._colorbar_mode == "each" or
(self._colorbar_mode == 'edge' and
row == self._nrows - 1)) and
self._colorbar_location == "top"):
v.append(Size.from_any(self._colorbar_pad, sz))
v_cb_pos.append(len(v))
v.append(Size.from_any(self._colorbar_size, sz))
for i in range(self.ngrids):
col, row = self._get_col_row(i)
#locator = self._divider.new_locator(nx=4*col,
# ny=2*(self._nrows - row - 1))
locator = self._divider.new_locator(nx=h_ax_pos[col],
ny=v_ax_pos[self._nrows-1-row])
self.axes_all[i].set_axes_locator(locator)
if self._colorbar_mode == "each":
if self._colorbar_location in ("right", "left"):
locator = self._divider.new_locator(
nx=h_cb_pos[col], ny=v_ax_pos[self._nrows - 1 - row])
elif self._colorbar_location in ("top", "bottom"):
locator = self._divider.new_locator(
nx=h_ax_pos[col], ny=v_cb_pos[self._nrows - 1 - row])
self.cbar_axes[i].set_axes_locator(locator)
elif self._colorbar_mode == 'edge':
if ((self._colorbar_location == 'left' and col == 0) or
(self._colorbar_location == 'right'
and col == self._ncols-1)):
locator = self._divider.new_locator(
nx=h_cb_pos[0], ny=v_ax_pos[self._nrows -1 - row])
self.cbar_axes[row].set_axes_locator(locator)
elif ((self._colorbar_location == 'bottom' and
row == self._nrows - 1) or
(self._colorbar_location == 'top' and row == 0)):
locator = self._divider.new_locator(nx=h_ax_pos[col],
ny=v_cb_pos[0])
self.cbar_axes[col].set_axes_locator(locator)
if self._colorbar_mode == "single":
if self._colorbar_location == "right":
#sz = Size.Fraction(Size.AxesX(self.axes_llc), self._nrows)
sz = Size.Fraction(self._nrows, Size.AxesX(self.axes_llc))
h.append(Size.from_any(self._colorbar_pad, sz))
h.append(Size.from_any(self._colorbar_size, sz))
locator = self._divider.new_locator(nx=-2, ny=0, ny1=-1)
elif self._colorbar_location == "top":
#sz = Size.Fraction(Size.AxesY(self.axes_llc), self._ncols)
sz = Size.Fraction(self._ncols, Size.AxesY(self.axes_llc))
v.append(Size.from_any(self._colorbar_pad, sz))
v.append(Size.from_any(self._colorbar_size, sz))
locator = self._divider.new_locator(nx=0, nx1=-1, ny=-2)
if self._colorbar_location in ("right", "top"):
for i in range(self.ngrids):
self.cbar_axes[i].set_visible(False)
self.cbar_axes[0].set_axes_locator(locator)
self.cbar_axes[0].set_visible(True)
elif self._colorbar_mode == "each":
for i in range(self.ngrids):
self.cbar_axes[i].set_visible(True)
elif self._colorbar_mode == "edge":
if self._colorbar_location in ('right', 'left'):
count = self._nrows
else:
count = self._ncols
for i in range(count):
self.cbar_axes[i].set_visible(True)
for j in range(i + 1, self.ngrids):
self.cbar_axes[j].set_visible(False)
else:
for i in range(self.ngrids):
self.cbar_axes[i].set_visible(False)
self.cbar_axes[i].set_position([1., 1., 0.001, 0.001],
which="active")
self._divider.set_horizontal(h)
self._divider.set_vertical(v)
AxesGrid = ImageGrid
@@ -0,0 +1,220 @@
import numpy as np
from .axes_divider import make_axes_locatable, Size
from .mpl_axes import Axes
def make_rgb_axes(ax, pad=0.01, axes_class=None, add_all=True):
"""
pad : fraction of the axes height.
"""
divider = make_axes_locatable(ax)
pad_size = Size.Fraction(pad, Size.AxesY(ax))
xsize = Size.Fraction((1.-2.*pad)/3., Size.AxesX(ax))
ysize = Size.Fraction((1.-2.*pad)/3., Size.AxesY(ax))
divider.set_horizontal([Size.AxesX(ax), pad_size, xsize])
divider.set_vertical([ysize, pad_size, ysize, pad_size, ysize])
ax.set_axes_locator(divider.new_locator(0, 0, ny1=-1))
ax_rgb = []
if axes_class is None:
try:
axes_class = ax._axes_class
except AttributeError:
axes_class = type(ax)
for ny in [4, 2, 0]:
ax1 = axes_class(ax.get_figure(),
ax.get_position(original=True),
sharex=ax, sharey=ax)
locator = divider.new_locator(nx=2, ny=ny)
ax1.set_axes_locator(locator)
for t in ax1.yaxis.get_ticklabels() + ax1.xaxis.get_ticklabels():
t.set_visible(False)
try:
for axis in ax1.axis.values():
axis.major_ticklabels.set_visible(False)
except AttributeError:
pass
ax_rgb.append(ax1)
if add_all:
fig = ax.get_figure()
for ax1 in ax_rgb:
fig.add_axes(ax1)
return ax_rgb
def imshow_rgb(ax, r, g, b, **kwargs):
ny, nx = r.shape
R = np.zeros([ny, nx, 3], dtype="d")
R[:,:,0] = r
G = np.zeros_like(R)
G[:,:,1] = g
B = np.zeros_like(R)
B[:,:,2] = b
RGB = R + G + B
im_rgb = ax.imshow(RGB, **kwargs)
return im_rgb
class RGBAxesBase(object):
"""base class for a 4-panel imshow (RGB, R, G, B)
Layout:
+---------------+-----+
| | R |
+ +-----+
| RGB | G |
+ +-----+
| | B |
+---------------+-----+
Attributes
----------
_defaultAxesClass : matplotlib.axes.Axes
defaults to 'Axes' in RGBAxes child class.
No default in abstract base class
RGB : _defaultAxesClass
The axes object for the three-channel imshow
R : _defaultAxesClass
The axes object for the red channel imshow
G : _defaultAxesClass
The axes object for the green channel imshow
B : _defaultAxesClass
The axes object for the blue channel imshow
"""
def __init__(self, *args, pad=0, add_all=True, **kwargs):
"""
Parameters
----------
pad : float
fraction of the axes height to put as padding.
defaults to 0.0
add_all : bool
True: Add the {rgb, r, g, b} axes to the figure
defaults to True.
axes_class : matplotlib.axes.Axes
kl :
Unpacked into axes_class() init for RGB
kwargs :
Unpacked into axes_class() init for RGB, R, G, B axes
"""
try:
axes_class = kwargs.pop("axes_class", self._defaultAxesClass)
except AttributeError:
raise AttributeError(
'A subclass of RGBAxesBase must have a _defaultAxesClass '
'attribute. If you are not sure which axes class to use, '
'consider using mpl_toolkits.axes_grid1.mpl_axes.Axes.'
)
ax = axes_class(*args, **kwargs)
divider = make_axes_locatable(ax)
pad_size = Size.Fraction(pad, Size.AxesY(ax))
xsize = Size.Fraction((1.-2.*pad)/3., Size.AxesX(ax))
ysize = Size.Fraction((1.-2.*pad)/3., Size.AxesY(ax))
divider.set_horizontal([Size.AxesX(ax), pad_size, xsize])
divider.set_vertical([ysize, pad_size, ysize, pad_size, ysize])
ax.set_axes_locator(divider.new_locator(0, 0, ny1=-1))
ax_rgb = []
for ny in [4, 2, 0]:
ax1 = axes_class(ax.get_figure(),
ax.get_position(original=True),
sharex=ax, sharey=ax, **kwargs)
locator = divider.new_locator(nx=2, ny=ny)
ax1.set_axes_locator(locator)
ax1.axis[:].toggle(ticklabels=False)
ax_rgb.append(ax1)
self.RGB = ax
self.R, self.G, self.B = ax_rgb
if add_all:
fig = ax.get_figure()
fig.add_axes(ax)
self.add_RGB_to_figure()
self._config_axes()
def _config_axes(self, line_color='w', marker_edge_color='w'):
"""Set the line color and ticks for the axes
Parameters
----------
line_color : any matplotlib color
marker_edge_color : any matplotlib color
"""
for ax1 in [self.RGB, self.R, self.G, self.B]:
ax1.axis[:].line.set_color(line_color)
ax1.axis[:].major_ticks.set_markeredgecolor(marker_edge_color)
def add_RGB_to_figure(self):
"""Add the red, green and blue axes to the RGB composite's axes figure
"""
self.RGB.get_figure().add_axes(self.R)
self.RGB.get_figure().add_axes(self.G)
self.RGB.get_figure().add_axes(self.B)
def imshow_rgb(self, r, g, b, **kwargs):
"""Create the four images {rgb, r, g, b}
Parameters
----------
r : array-like
The red array
g : array-like
The green array
b : array-like
The blue array
kwargs : imshow kwargs
kwargs get unpacked into the imshow calls for the four images
Returns
-------
rgb : matplotlib.image.AxesImage
r : matplotlib.image.AxesImage
g : matplotlib.image.AxesImage
b : matplotlib.image.AxesImage
"""
if not (r.shape == g.shape == b.shape):
raise ValueError('Input shapes do not match.'
'\nr.shape = {}'
'\ng.shape = {}'
'\nb.shape = {}'
.format(r.shape, g.shape, b.shape))
RGB = np.dstack([r, g, b])
R = np.zeros_like(RGB)
R[:,:,0] = r
G = np.zeros_like(RGB)
G[:,:,1] = g
B = np.zeros_like(RGB)
B[:,:,2] = b
im_rgb = self.RGB.imshow(RGB, **kwargs)
im_r = self.R.imshow(R, **kwargs)
im_g = self.G.imshow(G, **kwargs)
im_b = self.B.imshow(B, **kwargs)
return im_rgb, im_r, im_g, im_b
class RGBAxes(RGBAxesBase):
_defaultAxesClass = Axes
@@ -0,0 +1,328 @@
"""
provides a classes of simple units that will be used with AxesDivider
class (or others) to determine the size of each axes. The unit
classes define `get_size` method that returns a tuple of two floats,
meaning relative and absolute sizes, respectively.
Note that this class is nothing more than a simple tuple of two
floats. Take a look at the Divider class to see how these two
values are used.
"""
from numbers import Number
from matplotlib.axes import Axes
class _Base(object):
"Base class"
def __rmul__(self, other):
float(other) # just to check if number if given
return Fraction(other, self)
def __add__(self, other):
if isinstance(other, _Base):
return Add(self, other)
else:
float(other)
other = Fixed(other)
return Add(self, other)
class Add(_Base):
def __init__(self, a, b):
self._a = a
self._b = b
def get_size(self, renderer):
a_rel_size, a_abs_size = self._a.get_size(renderer)
b_rel_size, b_abs_size = self._b.get_size(renderer)
return a_rel_size + b_rel_size, a_abs_size + b_abs_size
class AddList(_Base):
def __init__(self, add_list):
self._list = add_list
def get_size(self, renderer):
sum_rel_size = sum([a.get_size(renderer)[0] for a in self._list])
sum_abs_size = sum([a.get_size(renderer)[1] for a in self._list])
return sum_rel_size, sum_abs_size
class Fixed(_Base):
"Simple fixed size with absolute part = *fixed_size* and relative part = 0"
def __init__(self, fixed_size):
self.fixed_size = fixed_size
def get_size(self, renderer):
rel_size = 0.
abs_size = self.fixed_size
return rel_size, abs_size
class Scaled(_Base):
"Simple scaled(?) size with absolute part = 0 and relative part = *scalable_size*"
def __init__(self, scalable_size):
self._scalable_size = scalable_size
def get_size(self, renderer):
rel_size = self._scalable_size
abs_size = 0.
return rel_size, abs_size
Scalable = Scaled
def _get_axes_aspect(ax):
aspect = ax.get_aspect()
# when aspec is "auto", consider it as 1.
if aspect in ('normal', 'auto'):
aspect = 1.
elif aspect == "equal":
aspect = 1
else:
aspect = float(aspect)
return aspect
class AxesX(_Base):
"""
Scaled size whose relative part corresponds to the data width
of the *axes* multiplied by the *aspect*.
"""
def __init__(self, axes, aspect=1., ref_ax=None):
self._axes = axes
self._aspect = aspect
if aspect == "axes" and ref_ax is None:
raise ValueError("ref_ax must be set when aspect='axes'")
self._ref_ax = ref_ax
def get_size(self, renderer):
l1, l2 = self._axes.get_xlim()
if self._aspect == "axes":
ref_aspect = _get_axes_aspect(self._ref_ax)
aspect = ref_aspect/_get_axes_aspect(self._axes)
else:
aspect = self._aspect
rel_size = abs(l2-l1)*aspect
abs_size = 0.
return rel_size, abs_size
class AxesY(_Base):
"""
Scaled size whose relative part corresponds to the data height
of the *axes* multiplied by the *aspect*.
"""
def __init__(self, axes, aspect=1., ref_ax=None):
self._axes = axes
self._aspect = aspect
if aspect == "axes" and ref_ax is None:
raise ValueError("ref_ax must be set when aspect='axes'")
self._ref_ax = ref_ax
def get_size(self, renderer):
l1, l2 = self._axes.get_ylim()
if self._aspect == "axes":
ref_aspect = _get_axes_aspect(self._ref_ax)
aspect = _get_axes_aspect(self._axes)
else:
aspect = self._aspect
rel_size = abs(l2-l1)*aspect
abs_size = 0.
return rel_size, abs_size
class MaxExtent(_Base):
"""
Size whose absolute part is the largest width (or height) of
the given *artist_list*.
"""
def __init__(self, artist_list, w_or_h):
self._artist_list = artist_list
if w_or_h not in ["width", "height"]:
raise ValueError()
self._w_or_h = w_or_h
def add_artist(self, a):
self._artist_list.append(a)
def get_size(self, renderer):
rel_size = 0.
w_list, h_list = [], []
for a in self._artist_list:
bb = a.get_window_extent(renderer)
w_list.append(bb.width)
h_list.append(bb.height)
dpi = a.get_figure().get_dpi()
if self._w_or_h == "width":
abs_size = max(w_list)/dpi
elif self._w_or_h == "height":
abs_size = max(h_list)/dpi
return rel_size, abs_size
class MaxWidth(_Base):
"""
Size whose absolute part is the largest width of
the given *artist_list*.
"""
def __init__(self, artist_list):
self._artist_list = artist_list
def add_artist(self, a):
self._artist_list.append(a)
def get_size(self, renderer):
rel_size = 0.
w_list = []
for a in self._artist_list:
bb = a.get_window_extent(renderer)
w_list.append(bb.width)
dpi = a.get_figure().get_dpi()
abs_size = max(w_list)/dpi
return rel_size, abs_size
class MaxHeight(_Base):
"""
Size whose absolute part is the largest height of
the given *artist_list*.
"""
def __init__(self, artist_list):
self._artist_list = artist_list
def add_artist(self, a):
self._artist_list.append(a)
def get_size(self, renderer):
rel_size = 0.
h_list = []
for a in self._artist_list:
bb = a.get_window_extent(renderer)
h_list.append(bb.height)
dpi = a.get_figure().get_dpi()
abs_size = max(h_list)/dpi
return rel_size, abs_size
class Fraction(_Base):
"""
An instance whose size is a *fraction* of the *ref_size*.
::
>>> s = Fraction(0.3, AxesX(ax))
"""
def __init__(self, fraction, ref_size):
self._fraction_ref = ref_size
self._fraction = fraction
def get_size(self, renderer):
if self._fraction_ref is None:
return self._fraction, 0.
else:
r, a = self._fraction_ref.get_size(renderer)
rel_size = r*self._fraction
abs_size = a*self._fraction
return rel_size, abs_size
class Padded(_Base):
"""
Return a instance where the absolute part of *size* is
increase by the amount of *pad*.
"""
def __init__(self, size, pad):
self._size = size
self._pad = pad
def get_size(self, renderer):
r, a = self._size.get_size(renderer)
rel_size = r
abs_size = a + self._pad
return rel_size, abs_size
def from_any(size, fraction_ref=None):
"""
Creates Fixed unit when the first argument is a float, or a
Fraction unit if that is a string that ends with %. The second
argument is only meaningful when Fraction unit is created.::
>>> a = Size.from_any(1.2) # => Size.Fixed(1.2)
>>> Size.from_any("50%", a) # => Size.Fraction(0.5, a)
"""
if isinstance(size, Number):
return Fixed(size)
elif isinstance(size, str):
if size[-1] == "%":
return Fraction(float(size[:-1]) / 100, fraction_ref)
raise ValueError("Unknown format")
class SizeFromFunc(_Base):
def __init__(self, func):
self._func = func
def get_size(self, renderer):
rel_size = 0.
bb = self._func(renderer)
dpi = renderer.points_to_pixels(72.)
abs_size = bb/dpi
return rel_size, abs_size
class GetExtentHelper(object):
def _get_left(self, axes_bbox):
return axes_bbox.xmin - self.xmin
def _get_right(self, axes_bbox):
return self.xmax - axes_bbox.xmax
def _get_bottom(self, axes_bbox):
return axes_bbox.ymin - self.ymin
def _get_top(self, axes_bbox):
return self.ymax - axes_bbox.ymax
_get_func_map = dict(left=_get_left,
right=_get_right,
bottom=_get_bottom,
top=_get_top)
del _get_left, _get_right, _get_bottom, _get_top
def __init__(self, ax, direction):
if isinstance(ax, Axes):
self._ax_list = [ax]
else:
self._ax_list = ax
try:
self._get_func = self._get_func_map[direction]
except KeyError:
raise KeyError("direction must be one of left, right, bottom, top")
def __call__(self, renderer):
vl = [self._get_func(ax.get_tightbbox(renderer,
call_axes_locator=False),
ax.bbox)
for ax in self._ax_list]
return max(vl)
@@ -0,0 +1,821 @@
"""
Colorbar toolkit with two classes and a function:
:class:`ColorbarBase`
the base class with full colorbar drawing functionality.
It can be used as-is to make a colorbar for a given colormap;
a mappable object (e.g., image) is not needed.
:class:`Colorbar`
the derived class for use with images or contour plots.
:func:`make_axes`
a function for resizing an axes and adding a second axes
suitable for a colorbar
The :meth:`~matplotlib.figure.Figure.colorbar` method uses :func:`make_axes`
and :class:`Colorbar`; the :func:`~matplotlib.pyplot.colorbar` function
is a thin wrapper over :meth:`~matplotlib.figure.Figure.colorbar`.
"""
import numpy as np
import matplotlib as mpl
import matplotlib.colors as colors
import matplotlib.cm as cm
from matplotlib import docstring
import matplotlib.ticker as ticker
import matplotlib.cbook as cbook
import matplotlib.collections as collections
import matplotlib.contour as contour
from matplotlib.path import Path
from matplotlib.patches import PathPatch
from matplotlib.transforms import Bbox
make_axes_kw_doc = '''
============= ====================================================
Property Description
============= ====================================================
*orientation* vertical or horizontal
*fraction* 0.15; fraction of original axes to use for colorbar
*pad* 0.05 if vertical, 0.15 if horizontal; fraction
of original axes between colorbar and new image axes
*shrink* 1.0; fraction by which to shrink the colorbar
*aspect* 20; ratio of long to short dimensions
============= ====================================================
'''
colormap_kw_doc = '''
=========== ====================================================
Property Description
=========== ====================================================
*extend* [ 'neither' | 'both' | 'min' | 'max' ]
If not 'neither', make pointed end(s) for out-of-
range values. These are set for a given colormap
using the colormap set_under and set_over methods.
*spacing* [ 'uniform' | 'proportional' ]
Uniform spacing gives each discrete color the same
space; proportional makes the space proportional to
the data interval.
*ticks* [ None | list of ticks | Locator object ]
If None, ticks are determined automatically from the
input.
*format* [ None | format string | Formatter object ]
If None, the
:class:`~matplotlib.ticker.ScalarFormatter` is used.
If a format string is given, e.g., '%.3f', that is
used. An alternative
:class:`~matplotlib.ticker.Formatter` object may be
given instead.
*drawedges* bool
Whether to draw lines at color boundaries.
=========== ====================================================
The following will probably be useful only in the context of
indexed colors (that is, when the mappable has norm=NoNorm()),
or other unusual circumstances.
============ ===================================================
Property Description
============ ===================================================
*boundaries* None or a sequence
*values* None or a sequence which must be of length 1 less
than the sequence of *boundaries*. For each region
delimited by adjacent entries in *boundaries*, the
color mapped to the corresponding value in values
will be used.
============ ===================================================
'''
colorbar_doc = '''
Add a colorbar to a plot.
Function signatures for the :mod:`~matplotlib.pyplot` interface; all
but the first are also method signatures for the
:meth:`~matplotlib.figure.Figure.colorbar` method::
colorbar(**kwargs)
colorbar(mappable, **kwargs)
colorbar(mappable, cax=cax, **kwargs)
colorbar(mappable, ax=ax, **kwargs)
arguments:
*mappable*
the :class:`~matplotlib.image.Image`,
:class:`~matplotlib.contour.ContourSet`, etc. to
which the colorbar applies; this argument is mandatory for the
:meth:`~matplotlib.figure.Figure.colorbar` method but optional for the
:func:`~matplotlib.pyplot.colorbar` function, which sets the
default to the current image.
keyword arguments:
*cax*
None | axes object into which the colorbar will be drawn
*ax*
None | parent axes object from which space for a new
colorbar axes will be stolen
Additional keyword arguments are of two kinds:
axes properties:
%s
colorbar properties:
%s
If *mappable* is a :class:`~matplotlib.contours.ContourSet`, its *extend*
kwarg is included automatically.
Note that the *shrink* kwarg provides a simple way to keep a vertical
colorbar, for example, from being taller than the axes of the mappable
to which the colorbar is attached; but it is a manual method requiring
some trial and error. If the colorbar is too tall (or a horizontal
colorbar is too wide) use a smaller value of *shrink*.
For more precise control, you can manually specify the positions of
the axes objects in which the mappable and the colorbar are drawn. In
this case, do not use any of the axes properties kwargs.
It is known that some vector graphics viewer (svg and pdf) renders white gaps
between segments of the colorbar. This is due to bugs in the viewers not
matplotlib. As a workaround the colorbar can be rendered with overlapping
segments::
cbar = colorbar()
cbar.solids.set_edgecolor("face")
draw()
However this has negative consequences in other circumstances. Particularly with
semi transparent images (alpha < 1) and colorbar extensions and is not enabled
by default see (issue #1188).
returns:
:class:`~matplotlib.colorbar.Colorbar` instance; see also its base class,
:class:`~matplotlib.colorbar.ColorbarBase`. Call the
:meth:`~matplotlib.colorbar.ColorbarBase.set_label` method
to label the colorbar.
The transData of the *cax* is adjusted so that the limits in the
longest axis actually corresponds to the limits in colorbar range. On
the other hand, the shortest axis has a data limits of [1,2], whose
unconventional value is to prevent underflow when log scale is used.
''' % (make_axes_kw_doc, colormap_kw_doc)
#docstring.interpd.update(colorbar_doc=colorbar_doc)
class CbarAxesLocator(object):
"""
CbarAxesLocator is a axes_locator for colorbar axes. It adjust the
position of the axes to make a room for extended ends, i.e., the
extended ends are located outside the axes area.
"""
def __init__(self, locator=None, extend="neither", orientation="vertical"):
"""
*locator* : the bbox returned from the locator is used as a
initial axes location. If None, axes.bbox is used.
*extend* : same as in ColorbarBase
*orientation* : same as in ColorbarBase
"""
self._locator = locator
self.extesion_fraction = 0.05
self.extend = extend
self.orientation = orientation
def get_original_position(self, axes, renderer):
"""
get the original position of the axes.
"""
if self._locator is None:
bbox = axes.get_position(original=True)
else:
bbox = self._locator(axes, renderer)
return bbox
def get_end_vertices(self):
"""
return a tuple of two vertices for the colorbar extended ends.
The first vertices is for the minimum end, and the second is for
the maximum end.
"""
# Note that concatenating two vertices needs to make a
# vertices for the frame.
extesion_fraction = self.extesion_fraction
corx = extesion_fraction*2.
cory = 1./(1. - corx)
x1, y1, w, h = 0, 0, 1, 1
x2, y2 = x1 + w, y1 + h
dw, dh = w*extesion_fraction, h*extesion_fraction*cory
if self.extend in ["min", "both"]:
bottom = [(x1, y1),
(x1+w/2., y1-dh),
(x2, y1)]
else:
bottom = [(x1, y1),
(x2, y1)]
if self.extend in ["max", "both"]:
top = [(x2, y2),
(x1+w/2., y2+dh),
(x1, y2)]
else:
top = [(x2, y2),
(x1, y2)]
if self.orientation == "horizontal":
bottom = [(y,x) for (x,y) in bottom]
top = [(y,x) for (x,y) in top]
return bottom, top
def get_path_patch(self):
"""
get the path for axes patch
"""
end1, end2 = self.get_end_vertices()
verts = [] + end1 + end2 + end1[:1]
return Path(verts)
def get_path_ends(self):
"""
get the paths for extended ends
"""
end1, end2 = self.get_end_vertices()
return Path(end1), Path(end2)
def __call__(self, axes, renderer):
"""
Return the adjusted position of the axes
"""
bbox0 = self.get_original_position(axes, renderer)
bbox = bbox0
x1, y1, w, h = bbox.bounds
extesion_fraction = self.extesion_fraction
dw, dh = w*extesion_fraction, h*extesion_fraction
if self.extend in ["min", "both"]:
if self.orientation == "horizontal":
x1 = x1 + dw
else:
y1 = y1+dh
if self.extend in ["max", "both"]:
if self.orientation == "horizontal":
w = w-2*dw
else:
h = h-2*dh
return Bbox.from_bounds(x1, y1, w, h)
class ColorbarBase(cm.ScalarMappable):
'''
Draw a colorbar in an existing axes.
This is a base class for the :class:`Colorbar` class, which is the
basis for the :func:`~matplotlib.pyplot.colorbar` method and pyplot
function.
It is also useful by itself for showing a colormap. If the *cmap*
kwarg is given but *boundaries* and *values* are left as None,
then the colormap will be displayed on a 0-1 scale. To show the
under- and over-value colors, specify the *norm* as::
colors.Normalize(clip=False)
To show the colors versus index instead of on the 0-1 scale,
use::
norm=colors.NoNorm.
Useful attributes:
:attr:`ax`
the Axes instance in which the colorbar is drawn
:attr:`lines`
a LineCollection if lines were drawn, otherwise None
:attr:`dividers`
a LineCollection if *drawedges* is True, otherwise None
Useful public methods are :meth:`set_label` and :meth:`add_lines`.
'''
def __init__(self, ax, cmap=None,
norm=None,
alpha=1.0,
values=None,
boundaries=None,
orientation='vertical',
extend='neither',
spacing='uniform', # uniform or proportional
ticks=None,
format=None,
drawedges=False,
filled=True,
):
self.ax = ax
if cmap is None: cmap = cm.get_cmap()
if norm is None: norm = colors.Normalize()
self.alpha = alpha
cm.ScalarMappable.__init__(self, cmap=cmap, norm=norm)
self.values = values
self.boundaries = boundaries
self.extend = extend
self.spacing = spacing
self.orientation = orientation
self.drawedges = drawedges
self.filled = filled
# artists
self.solids = None
self.lines = None
self.dividers = None
self.extension_patch1 = None
self.extension_patch2 = None
if orientation == "vertical":
self.cbar_axis = self.ax.yaxis
else:
self.cbar_axis = self.ax.xaxis
if format is None:
if isinstance(self.norm, colors.LogNorm):
# change both axis for proper aspect
self.ax.set_xscale("log")
self.ax.set_yscale("log")
self.cbar_axis.set_minor_locator(ticker.NullLocator())
formatter = ticker.LogFormatter()
else:
formatter = None
elif isinstance(format, str):
formatter = ticker.FormatStrFormatter(format)
else:
formatter = format # Assume it is a Formatter
if formatter is None:
formatter = self.cbar_axis.get_major_formatter()
else:
self.cbar_axis.set_major_formatter(formatter)
if cbook.iterable(ticks):
self.cbar_axis.set_ticks(ticks)
elif ticks is not None:
self.cbar_axis.set_major_locator(ticks)
else:
self._select_locator(formatter)
self._config_axes()
self.update_artists()
self.set_label_text('')
def _get_colorbar_limits(self):
"""
initial limits for colorbar range. The returned min, max values
will be used to create colorbar solid(?) and etc.
"""
if self.boundaries is not None:
C = self.boundaries
if self.extend in ["min", "both"]:
C = C[1:]
if self.extend in ["max", "both"]:
C = C[:-1]
return min(C), max(C)
else:
return self.get_clim()
def _config_axes(self):
'''
Adjust the properties of the axes to be adequate for colorbar display.
'''
ax = self.ax
axes_locator = CbarAxesLocator(ax.get_axes_locator(),
extend=self.extend,
orientation=self.orientation)
ax.set_axes_locator(axes_locator)
# override the get_data_ratio for the aspect works.
def _f():
return 1.
ax.get_data_ratio = _f
ax.get_data_ratio_log = _f
ax.set_frame_on(True)
ax.set_navigate(False)
self.ax.set_autoscalex_on(False)
self.ax.set_autoscaley_on(False)
if self.orientation == 'horizontal':
ax.xaxis.set_label_position('bottom')
ax.set_yticks([])
else:
ax.set_xticks([])
ax.yaxis.set_label_position('right')
ax.yaxis.set_ticks_position('right')
def update_artists(self):
"""
Update the colorbar associated artists, *filled* and
*ends*. Note that *lines* are not updated. This needs to be
called whenever clim of associated image changes.
"""
self._process_values()
self._add_ends()
X, Y = self._mesh()
if self.filled:
C = self._values[:,np.newaxis]
self._add_solids(X, Y, C)
ax = self.ax
vmin, vmax = self._get_colorbar_limits()
if self.orientation == 'horizontal':
ax.set_ylim(1, 2)
ax.set_xlim(vmin, vmax)
else:
ax.set_xlim(1, 2)
ax.set_ylim(vmin, vmax)
def _add_ends(self):
"""
Create patches from extended ends and add them to the axes.
"""
del self.extension_patch1
del self.extension_patch2
path1, path2 = self.ax.get_axes_locator().get_path_ends()
fc=mpl.rcParams['axes.facecolor']
ec=mpl.rcParams['axes.edgecolor']
linewidths=0.5*mpl.rcParams['axes.linewidth']
self.extension_patch1 = PathPatch(path1,
fc=fc, ec=ec, lw=linewidths,
zorder=2.,
transform=self.ax.transAxes,
clip_on=False)
self.extension_patch2 = PathPatch(path2,
fc=fc, ec=ec, lw=linewidths,
zorder=2.,
transform=self.ax.transAxes,
clip_on=False)
self.ax.add_artist(self.extension_patch1)
self.ax.add_artist(self.extension_patch2)
def _set_label_text(self):
"""
set label.
"""
self.cbar_axis.set_label_text(self._label, **self._labelkw)
def set_label_text(self, label, **kw):
'''
Label the long axis of the colorbar
'''
self._label = label
self._labelkw = kw
self._set_label_text()
def _edges(self, X, Y):
'''
Return the separator line segments; helper for _add_solids.
'''
N = X.shape[0]
# Using the non-array form of these line segments is much
# simpler than making them into arrays.
if self.orientation == 'vertical':
return [list(zip(X[i], Y[i])) for i in range(1, N-1)]
else:
return [list(zip(Y[i], X[i])) for i in range(1, N-1)]
def _add_solids(self, X, Y, C):
'''
Draw the colors using :meth:`~matplotlib.axes.Axes.pcolormesh`;
optionally add separators.
'''
## Change to pcolorfast after fixing bugs in some backends...
if self.extend in ["min", "both"]:
cc = self.to_rgba([C[0][0]])
self.extension_patch1.set_fc(cc[0])
X, Y, C = X[1:], Y[1:], C[1:]
if self.extend in ["max", "both"]:
cc = self.to_rgba([C[-1][0]])
self.extension_patch2.set_fc(cc[0])
X, Y, C = X[:-1], Y[:-1], C[:-1]
if self.orientation == 'vertical':
args = (X, Y, C)
else:
args = (np.transpose(Y), np.transpose(X), np.transpose(C))
kw = {'cmap':self.cmap, 'norm':self.norm,
'shading':'flat', 'alpha':self.alpha,
}
del self.solids
del self.dividers
col = self.ax.pcolormesh(*args, **kw)
self.solids = col
if self.drawedges:
self.dividers = collections.LineCollection(
self._edges(X,Y),
colors=(mpl.rcParams['axes.edgecolor'],),
linewidths=(0.5*mpl.rcParams['axes.linewidth'],),
)
self.ax.add_collection(self.dividers)
else:
self.dividers = None
def add_lines(self, levels, colors, linewidths):
'''
Draw lines on the colorbar. It deletes preexisting lines.
'''
X, Y = np.meshgrid([1, 2], levels)
if self.orientation == 'vertical':
xy = np.stack([X, Y], axis=-1)
else:
xy = np.stack([Y, X], axis=-1)
col = collections.LineCollection(xy, linewidths=linewidths)
self.lines = col
col.set_color(colors)
self.ax.add_collection(col)
def _select_locator(self, formatter):
'''
select a suitable locator
'''
if self.boundaries is None:
if isinstance(self.norm, colors.NoNorm):
nv = len(self._values)
base = 1 + int(nv/10)
locator = ticker.IndexLocator(base=base, offset=0)
elif isinstance(self.norm, colors.BoundaryNorm):
b = self.norm.boundaries
locator = ticker.FixedLocator(b, nbins=10)
elif isinstance(self.norm, colors.LogNorm):
locator = ticker.LogLocator()
else:
locator = ticker.MaxNLocator(nbins=5)
else:
b = self._boundaries[self._inside]
locator = ticker.FixedLocator(b) #, nbins=10)
self.cbar_axis.set_major_locator(locator)
def _process_values(self, b=None):
'''
Set the :attr:`_boundaries` and :attr:`_values` attributes
based on the input boundaries and values. Input boundaries
can be *self.boundaries* or the argument *b*.
'''
if b is None:
b = self.boundaries
if b is not None:
self._boundaries = np.asarray(b, dtype=float)
if self.values is None:
self._values = 0.5*(self._boundaries[:-1]
+ self._boundaries[1:])
if isinstance(self.norm, colors.NoNorm):
self._values = (self._values + 0.00001).astype(np.int16)
return
self._values = np.array(self.values)
return
if self.values is not None:
self._values = np.array(self.values)
if self.boundaries is None:
b = np.zeros(len(self.values)+1, 'd')
b[1:-1] = 0.5*(self._values[:-1] - self._values[1:])
b[0] = 2.0*b[1] - b[2]
b[-1] = 2.0*b[-2] - b[-3]
self._boundaries = b
return
self._boundaries = np.array(self.boundaries)
return
# Neither boundaries nor values are specified;
# make reasonable ones based on cmap and norm.
if isinstance(self.norm, colors.NoNorm):
b = self._uniform_y(self.cmap.N+1) * self.cmap.N - 0.5
v = np.zeros((len(b)-1,), dtype=np.int16)
v = np.arange(self.cmap.N, dtype=np.int16)
self._boundaries = b
self._values = v
return
elif isinstance(self.norm, colors.BoundaryNorm):
b = np.array(self.norm.boundaries)
v = np.zeros((len(b)-1,), dtype=float)
bi = self.norm.boundaries
v = 0.5*(bi[:-1] + bi[1:])
self._boundaries = b
self._values = v
return
else:
b = self._uniform_y(self.cmap.N+1)
self._process_values(b)
def _uniform_y(self, N):
'''
Return colorbar data coordinates for *N* uniformly
spaced boundaries.
'''
vmin, vmax = self._get_colorbar_limits()
if isinstance(self.norm, colors.LogNorm):
y = np.logspace(np.log10(vmin), np.log10(vmax), N)
else:
y = np.linspace(vmin, vmax, N)
return y
def _mesh(self):
'''
Return X,Y, the coordinate arrays for the colorbar pcolormesh.
These are suitable for a vertical colorbar; swapping and
transposition for a horizontal colorbar are done outside
this function.
'''
x = np.array([1.0, 2.0])
if self.spacing == 'uniform':
y = self._uniform_y(len(self._boundaries))
else:
y = self._boundaries
self._y = y
X, Y = np.meshgrid(x,y)
return X, Y
def set_alpha(self, alpha):
"""
set alpha value.
"""
self.alpha = alpha
class Colorbar(ColorbarBase):
def __init__(self, ax, mappable, **kw):
mappable.autoscale_None() # Ensure mappable.norm.vmin, vmax
# are set when colorbar is called,
# even if mappable.draw has not yet
# been called. This will not change
# vmin, vmax if they are already set.
self.mappable = mappable
kw['cmap'] = mappable.cmap
kw['norm'] = mappable.norm
kw['alpha'] = mappable.get_alpha()
if isinstance(mappable, contour.ContourSet):
CS = mappable
kw['boundaries'] = CS._levels
kw['values'] = CS.cvalues
kw['extend'] = CS.extend
#kw['ticks'] = CS._levels
kw.setdefault('ticks', ticker.FixedLocator(CS.levels, nbins=10))
kw['filled'] = CS.filled
ColorbarBase.__init__(self, ax, **kw)
if not CS.filled:
self.add_lines(CS)
else:
ColorbarBase.__init__(self, ax, **kw)
def add_lines(self, CS):
'''
Add the lines from a non-filled
:class:`~matplotlib.contour.ContourSet` to the colorbar.
'''
if not isinstance(CS, contour.ContourSet) or CS.filled:
raise ValueError('add_lines is only for a ContourSet of lines')
tcolors = [c[0] for c in CS.tcolors]
tlinewidths = [t[0] for t in CS.tlinewidths]
# The following was an attempt to get the colorbar lines
# to follow subsequent changes in the contour lines,
# but more work is needed: specifically, a careful
# look at event sequences, and at how
# to make one object track another automatically.
#tcolors = [col.get_colors()[0] for col in CS.collections]
#tlinewidths = [col.get_linewidth()[0] for lw in CS.collections]
ColorbarBase.add_lines(self, CS.levels, tcolors, tlinewidths)
def update_bruteforce(self, mappable):
"""
Update the colorbar artists to reflect the change of the
associated mappable.
"""
self.update_artists()
if isinstance(mappable, contour.ContourSet):
if not mappable.filled:
self.add_lines(mappable)
@docstring.Substitution(make_axes_kw_doc)
def make_axes(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw):
'''
Resize and reposition a parent axes, and return a child
axes suitable for a colorbar
::
cax, kw = make_axes(parent, **kw)
Keyword arguments may include the following (with defaults):
*orientation*
'vertical' or 'horizontal'
%s
All but the first of these are stripped from the input kw set.
Returns (cax, kw), the child axes and the reduced kw dictionary.
'''
orientation = kw.setdefault('orientation', 'vertical')
#pb = transforms.PBox(parent.get_position())
pb = parent.get_position(original=True).frozen()
if orientation == 'vertical':
pad = kw.pop('pad', 0.05)
x1 = 1.0-fraction
pb1, pbx, pbcb = pb.splitx(x1-pad, x1)
pbcb = pbcb.shrunk(1.0, shrink).anchored('C', pbcb)
anchor = (0.0, 0.5)
panchor = (1.0, 0.5)
else:
pad = kw.pop('pad', 0.15)
pbcb, pbx, pb1 = pb.splity(fraction, fraction+pad)
pbcb = pbcb.shrunk(shrink, 1.0).anchored('C', pbcb)
aspect = 1.0/aspect
anchor = (0.5, 1.0)
panchor = (0.5, 0.0)
parent.set_position(pb1)
parent.set_anchor(panchor)
fig = parent.get_figure()
cax = fig.add_axes(pbcb)
cax.set_aspect(aspect, anchor=anchor, adjustable='box')
return cax, kw
@docstring.Substitution(colorbar_doc)
def colorbar(mappable, cax=None, ax=None, **kw):
"""
Create a colorbar for a ScalarMappable instance.
Documentation for the pyplot thin wrapper:
%s
"""
import matplotlib.pyplot as plt
if ax is None:
ax = plt.gca()
if cax is None:
cax, kw = make_axes(ax, **kw)
cb = Colorbar(cax, mappable, **kw)
def on_changed(m):
cb.set_cmap(m.get_cmap())
cb.set_clim(m.get_clim())
cb.update_bruteforce(m)
cbid = mappable.callbacksSM.connect('changed', on_changed)
mappable.colorbar = cb
ax.figure.sca(ax)
return cb
@@ -0,0 +1,680 @@
"""
A collection of functions and objects for creating or placing inset axes.
"""
import warnings
from matplotlib import docstring
from matplotlib.offsetbox import AnchoredOffsetbox
from matplotlib.patches import Patch, Rectangle
from matplotlib.path import Path
from matplotlib.transforms import Bbox, BboxTransformTo
from matplotlib.transforms import IdentityTransform, TransformedBbox
from . import axes_size as Size
from .parasite_axes import HostAxes
class InsetPosition(object):
@docstring.dedent_interpd
def __init__(self, parent, lbwh):
"""
An object for positioning an inset axes.
This is created by specifying the normalized coordinates in the axes,
instead of the figure.
Parameters
----------
parent : `matplotlib.axes.Axes`
Axes to use for normalizing coordinates.
lbwh : iterable of four floats
The left edge, bottom edge, width, and height of the inset axes, in
units of the normalized coordinate of the *parent* axes.
See Also
--------
:meth:`matplotlib.axes.Axes.set_axes_locator`
Examples
--------
The following bounds the inset axes to a box with 20%% of the parent
axes's height and 40%% of the width. The size of the axes specified
([0, 0, 1, 1]) ensures that the axes completely fills the bounding box:
>>> parent_axes = plt.gca()
>>> ax_ins = plt.axes([0, 0, 1, 1])
>>> ip = InsetPosition(ax, [0.5, 0.1, 0.4, 0.2])
>>> ax_ins.set_axes_locator(ip)
"""
self.parent = parent
self.lbwh = lbwh
def __call__(self, ax, renderer):
bbox_parent = self.parent.get_position(original=False)
trans = BboxTransformTo(bbox_parent)
bbox_inset = Bbox.from_bounds(*self.lbwh)
bb = TransformedBbox(bbox_inset, trans)
return bb
class AnchoredLocatorBase(AnchoredOffsetbox):
def __init__(self, bbox_to_anchor, offsetbox, loc,
borderpad=0.5, bbox_transform=None):
super().__init__(
loc, pad=0., child=None, borderpad=borderpad,
bbox_to_anchor=bbox_to_anchor, bbox_transform=bbox_transform
)
def draw(self, renderer):
raise RuntimeError("No draw method should be called")
def __call__(self, ax, renderer):
self.axes = ax
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
self._update_offset_func(renderer, fontsize)
width, height, xdescent, ydescent = self.get_extent(renderer)
px, py = self.get_offset(width, height, 0, 0, renderer)
bbox_canvas = Bbox.from_bounds(px, py, width, height)
tr = ax.figure.transFigure.inverted()
bb = TransformedBbox(bbox_canvas, tr)
return bb
class AnchoredSizeLocator(AnchoredLocatorBase):
def __init__(self, bbox_to_anchor, x_size, y_size, loc,
borderpad=0.5, bbox_transform=None):
super().__init__(
bbox_to_anchor, None, loc,
borderpad=borderpad, bbox_transform=bbox_transform
)
self.x_size = Size.from_any(x_size)
self.y_size = Size.from_any(y_size)
def get_extent(self, renderer):
x, y, w, h = self.get_bbox_to_anchor().bounds
dpi = renderer.points_to_pixels(72.)
r, a = self.x_size.get_size(renderer)
width = w * r + a * dpi
r, a = self.y_size.get_size(renderer)
height = h * r + a * dpi
xd, yd = 0, 0
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
pad = self.pad * fontsize
return width + 2 * pad, height + 2 * pad, xd + pad, yd + pad
class AnchoredZoomLocator(AnchoredLocatorBase):
def __init__(self, parent_axes, zoom, loc,
borderpad=0.5,
bbox_to_anchor=None,
bbox_transform=None):
self.parent_axes = parent_axes
self.zoom = zoom
if bbox_to_anchor is None:
bbox_to_anchor = parent_axes.bbox
super().__init__(
bbox_to_anchor, None, loc, borderpad=borderpad,
bbox_transform=bbox_transform)
def get_extent(self, renderer):
bb = TransformedBbox(self.axes.viewLim,
self.parent_axes.transData)
x, y, w, h = bb.bounds
fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
pad = self.pad * fontsize
return abs(w * self.zoom) + 2 * pad, abs(h * self.zoom) + 2 * pad, pad, pad
class BboxPatch(Patch):
@docstring.dedent_interpd
def __init__(self, bbox, **kwargs):
"""
Patch showing the shape bounded by a Bbox.
Parameters
----------
bbox : `matplotlib.transforms.Bbox`
Bbox to use for the extents of this patch.
**kwargs
Patch properties. Valid arguments include:
%(Patch)s
"""
if "transform" in kwargs:
raise ValueError("transform should not be set")
kwargs["transform"] = IdentityTransform()
Patch.__init__(self, **kwargs)
self.bbox = bbox
def get_path(self):
x0, y0, x1, y1 = self.bbox.extents
verts = [(x0, y0),
(x1, y0),
(x1, y1),
(x0, y1),
(x0, y0),
(0, 0)]
codes = [Path.MOVETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.LINETO,
Path.CLOSEPOLY]
return Path(verts, codes)
get_path.__doc__ = Patch.get_path.__doc__
class BboxConnector(Patch):
@staticmethod
def get_bbox_edge_pos(bbox, loc):
"""
Helper function to obtain the location of a corner of a bbox
Parameters
----------
bbox : `matplotlib.transforms.Bbox`
loc : {1, 2, 3, 4}
Corner of *bbox*. Valid values are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4
Returns
-------
x, y : float
Coordinates of the corner specified by *loc*.
"""
x0, y0, x1, y1 = bbox.extents
if loc == 1:
return x1, y1
elif loc == 2:
return x0, y1
elif loc == 3:
return x0, y0
elif loc == 4:
return x1, y0
@staticmethod
def connect_bbox(bbox1, bbox2, loc1, loc2=None):
"""
Helper function to obtain a Path from one bbox to another.
Parameters
----------
bbox1, bbox2 : `matplotlib.transforms.Bbox`
Bounding boxes to connect.
loc1 : {1, 2, 3, 4}
Corner of *bbox1* to use. Valid values are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4
loc2 : {1, 2, 3, 4}, optional
Corner of *bbox2* to use. If None, defaults to *loc1*.
Valid values are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4
Returns
-------
path : `matplotlib.path.Path`
A line segment from the *loc1* corner of *bbox1* to the *loc2*
corner of *bbox2*.
"""
if isinstance(bbox1, Rectangle):
transform = bbox1.get_transform()
bbox1 = Bbox.from_bounds(0, 0, 1, 1)
bbox1 = TransformedBbox(bbox1, transform)
if isinstance(bbox2, Rectangle):
transform = bbox2.get_transform()
bbox2 = Bbox.from_bounds(0, 0, 1, 1)
bbox2 = TransformedBbox(bbox2, transform)
if loc2 is None:
loc2 = loc1
x1, y1 = BboxConnector.get_bbox_edge_pos(bbox1, loc1)
x2, y2 = BboxConnector.get_bbox_edge_pos(bbox2, loc2)
verts = [[x1, y1], [x2, y2]]
codes = [Path.MOVETO, Path.LINETO]
return Path(verts, codes)
@docstring.dedent_interpd
def __init__(self, bbox1, bbox2, loc1, loc2=None, **kwargs):
"""
Connect two bboxes with a straight line.
Parameters
----------
bbox1, bbox2 : `matplotlib.transforms.Bbox`
Bounding boxes to connect.
loc1 : {1, 2, 3, 4}
Corner of *bbox1* to draw the line. Valid values are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4
loc2 : {1, 2, 3, 4}, optional
Corner of *bbox2* to draw the line. If None, defaults to *loc1*.
Valid values are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4
**kwargs
Patch properties for the line drawn. Valid arguments include:
%(Patch)s
"""
if "transform" in kwargs:
raise ValueError("transform should not be set")
kwargs["transform"] = IdentityTransform()
if 'fill' in kwargs:
Patch.__init__(self, **kwargs)
else:
fill = ('fc' in kwargs) or ('facecolor' in kwargs) or ('color' in kwargs)
Patch.__init__(self, fill=fill, **kwargs)
self.bbox1 = bbox1
self.bbox2 = bbox2
self.loc1 = loc1
self.loc2 = loc2
def get_path(self):
return self.connect_bbox(self.bbox1, self.bbox2,
self.loc1, self.loc2)
get_path.__doc__ = Patch.get_path.__doc__
class BboxConnectorPatch(BboxConnector):
@docstring.dedent_interpd
def __init__(self, bbox1, bbox2, loc1a, loc2a, loc1b, loc2b, **kwargs):
"""
Connect two bboxes with a quadrilateral.
The quadrilateral is specified by two lines that start and end at corners
of the bboxes. The four sides of the quadrilateral are defined by the two
lines given, the line between the two corners specified in *bbox1* and the
line between the two corners specified in *bbox2*.
Parameters
----------
bbox1, bbox2 : `matplotlib.transforms.Bbox`
Bounding boxes to connect.
loc1a, loc2a : {1, 2, 3, 4}
Corners of *bbox1* and *bbox2* to draw the first line.
Valid values are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4
loc1b, loc2b : {1, 2, 3, 4}
Corners of *bbox1* and *bbox2* to draw the second line.
Valid values are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4
**kwargs
Patch properties for the line drawn:
%(Patch)s
"""
if "transform" in kwargs:
raise ValueError("transform should not be set")
BboxConnector.__init__(self, bbox1, bbox2, loc1a, loc2a, **kwargs)
self.loc1b = loc1b
self.loc2b = loc2b
def get_path(self):
path1 = self.connect_bbox(self.bbox1, self.bbox2, self.loc1, self.loc2)
path2 = self.connect_bbox(self.bbox2, self.bbox1,
self.loc2b, self.loc1b)
path_merged = [*path1.vertices, *path2.vertices, path1.vertices[0]]
return Path(path_merged)
get_path.__doc__ = BboxConnector.get_path.__doc__
def _add_inset_axes(parent_axes, inset_axes):
"""Helper function to add an inset axes and disable navigation in it"""
parent_axes.figure.add_axes(inset_axes)
inset_axes.set_navigate(False)
@docstring.dedent_interpd
def inset_axes(parent_axes, width, height, loc='upper right',
bbox_to_anchor=None, bbox_transform=None,
axes_class=None,
axes_kwargs=None,
borderpad=0.5):
"""
Create an inset axes with a given width and height.
Both sizes used can be specified either in inches or percentage.
For example,::
inset_axes(parent_axes, width='40%%', height='30%%', loc=3)
creates in inset axes in the lower left corner of *parent_axes* which spans
over 30%% in height and 40%% in width of the *parent_axes*. Since the usage
of `.inset_axes` may become slightly tricky when exceeding such standard
cases, it is recommended to read
:ref:`the examples <sphx_glr_gallery_axes_grid1_inset_locator_demo.py>`.
Notes
-----
The meaning of *bbox_to_anchor* and *bbox_to_transform* is interpreted
differently from that of legend. The value of bbox_to_anchor
(or the return value of its get_points method; the default is
*parent_axes.bbox*) is transformed by the bbox_transform (the default
is Identity transform) and then interpreted as points in the pixel
coordinate (which is dpi dependent).
Thus, following three calls are identical and creates an inset axes
with respect to the *parent_axes*::
axins = inset_axes(parent_axes, "30%%", "40%%")
axins = inset_axes(parent_axes, "30%%", "40%%",
bbox_to_anchor=parent_axes.bbox)
axins = inset_axes(parent_axes, "30%%", "40%%",
bbox_to_anchor=(0, 0, 1, 1),
bbox_transform=parent_axes.transAxes)
Parameters
----------
parent_axes : `matplotlib.axes.Axes`
Axes to place the inset axes.
width, height : float or str
Size of the inset axes to create. If a float is provided, it is
the size in inches, e.g. *width=1.3*. If a string is provided, it is
the size in relative units, e.g. *width='40%%'*. By default, i.e. if
neither *bbox_to_anchor* nor *bbox_transform* are specified, those
are relative to the parent_axes. Otherwise they are to be understood
relative to the bounding box provided via *bbox_to_anchor*.
loc : int or string, optional, default to 1
Location to place the inset axes. The valid locations are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4,
'right' : 5,
'center left' : 6,
'center right' : 7,
'lower center' : 8,
'upper center' : 9,
'center' : 10
bbox_to_anchor : tuple or `matplotlib.transforms.BboxBase`, optional
Bbox that the inset axes will be anchored to. If None,
a tuple of (0, 0, 1, 1) is used if *bbox_transform* is set
to *parent_axes.transAxes* or *parent_axes.figure.transFigure*.
Otherwise, *parent_axes.bbox* is used. If a tuple, can be either
[left, bottom, width, height], or [left, bottom].
If the kwargs *width* and/or *height* are specified in relative units,
the 2-tuple [left, bottom] cannot be used. Note that,
unless *bbox_transform* is set, the units of the bounding box
are interpreted in the pixel coordinate. When using *bbox_to_anchor*
with tuple, it almost always makes sense to also specify
a *bbox_transform*. This might often be the axes transform
*parent_axes.transAxes*.
bbox_transform : `matplotlib.transforms.Transform`, optional
Transformation for the bbox that contains the inset axes.
If None, a `.transforms.IdentityTransform` is used. The value
of *bbox_to_anchor* (or the return value of its get_points method)
is transformed by the *bbox_transform* and then interpreted
as points in the pixel coordinate (which is dpi dependent).
You may provide *bbox_to_anchor* in some normalized coordinate,
and give an appropriate transform (e.g., *parent_axes.transAxes*).
axes_class : `matplotlib.axes.Axes` type, optional
If specified, the inset axes created will be created with this class's
constructor.
axes_kwargs : dict, optional
Keyworded arguments to pass to the constructor of the inset axes.
Valid arguments include:
%(Axes)s
borderpad : float, optional
Padding between inset axes and the bbox_to_anchor. Defaults to 0.5.
The units are axes font size, i.e. for a default font size of 10 points
*borderpad = 0.5* is equivalent to a padding of 5 points.
Returns
-------
inset_axes : `axes_class`
Inset axes object created.
"""
if axes_class is None:
axes_class = HostAxes
if axes_kwargs is None:
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position())
else:
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(),
**axes_kwargs)
if bbox_transform in [parent_axes.transAxes,
parent_axes.figure.transFigure]:
if bbox_to_anchor is None:
warnings.warn("Using the axes or figure transform requires a "
"bounding box in the respective coordinates. "
"Using bbox_to_anchor=(0,0,1,1) now.")
bbox_to_anchor = (0, 0, 1, 1)
if bbox_to_anchor is None:
bbox_to_anchor = parent_axes.bbox
if isinstance(bbox_to_anchor, tuple) and \
(isinstance(width, str) or isinstance(height, str)):
if len(bbox_to_anchor) != 4:
raise ValueError("Using relative units for width or height "
"requires to provide a 4-tuple or a "
"`BBox` instance to `bbox_to_anchor.")
axes_locator = AnchoredSizeLocator(bbox_to_anchor,
width, height,
loc=loc,
bbox_transform=bbox_transform,
borderpad=borderpad)
inset_axes.set_axes_locator(axes_locator)
_add_inset_axes(parent_axes, inset_axes)
return inset_axes
@docstring.dedent_interpd
def zoomed_inset_axes(parent_axes, zoom, loc='upper right',
bbox_to_anchor=None, bbox_transform=None,
axes_class=None,
axes_kwargs=None,
borderpad=0.5):
"""
Create an anchored inset axes by scaling a parent axes. For usage, also see
:ref:`the examples <sphx_glr_gallery_axes_grid1_inset_locator_demo2.py>`.
Parameters
----------
parent_axes : `matplotlib.axes.Axes`
Axes to place the inset axes.
zoom : float
Scaling factor of the data axes. *zoom* > 1 will enlargen the
coordinates (i.e., "zoomed in"), while *zoom* < 1 will shrink the
coordinates (i.e., "zoomed out").
loc : int or string, optional, default to 1
Location to place the inset axes. The valid locations are::
'upper right' : 1,
'upper left' : 2,
'lower left' : 3,
'lower right' : 4,
'right' : 5,
'center left' : 6,
'center right' : 7,
'lower center' : 8,
'upper center' : 9,
'center' : 10
bbox_to_anchor : tuple or `matplotlib.transforms.BboxBase`, optional
Bbox that the inset axes will be anchored to. If None,
*parent_axes.bbox* is used. If a tuple, can be either
[left, bottom, width, height], or [left, bottom].
If the kwargs *width* and/or *height* are specified in relative units,
the 2-tuple [left, bottom] cannot be used. Note that
the units of the bounding box are determined through the transform
in use. When using *bbox_to_anchor* it almost always makes sense to
also specify a *bbox_transform*. This might often be the axes transform
*parent_axes.transAxes*.
bbox_transform : `matplotlib.transforms.Transform`, optional
Transformation for the bbox that contains the inset axes.
If None, a `.transforms.IdentityTransform` is used (i.e. pixel
coordinates). This is useful when not providing any argument to
*bbox_to_anchor*. When using *bbox_to_anchor* it almost always makes
sense to also specify a *bbox_transform*. This might often be the
axes transform *parent_axes.transAxes*. Inversely, when specifying
the axes- or figure-transform here, be aware that not specifying
*bbox_to_anchor* will use *parent_axes.bbox*, the units of which are
in display (pixel) coordinates.
axes_class : `matplotlib.axes.Axes` type, optional
If specified, the inset axes created will be created with this class's
constructor.
axes_kwargs : dict, optional
Keyworded arguments to pass to the constructor of the inset axes.
Valid arguments include:
%(Axes)s
borderpad : float, optional
Padding between inset axes and the bbox_to_anchor. Defaults to 0.5.
The units are axes font size, i.e. for a default font size of 10 points
*borderpad = 0.5* is equivalent to a padding of 5 points.
Returns
-------
inset_axes : `axes_class`
Inset axes object created.
"""
if axes_class is None:
axes_class = HostAxes
if axes_kwargs is None:
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position())
else:
inset_axes = axes_class(parent_axes.figure, parent_axes.get_position(),
**axes_kwargs)
axes_locator = AnchoredZoomLocator(parent_axes, zoom=zoom, loc=loc,
bbox_to_anchor=bbox_to_anchor,
bbox_transform=bbox_transform,
borderpad=borderpad)
inset_axes.set_axes_locator(axes_locator)
_add_inset_axes(parent_axes, inset_axes)
return inset_axes
@docstring.dedent_interpd
def mark_inset(parent_axes, inset_axes, loc1, loc2, **kwargs):
"""
Draw a box to mark the location of an area represented by an inset axes.
This function draws a box in *parent_axes* at the bounding box of
*inset_axes*, and shows a connection with the inset axes by drawing lines
at the corners, giving a "zoomed in" effect.
Parameters
----------
parent_axes : `matplotlib.axes.Axes`
Axes which contains the area of the inset axes.
inset_axes : `matplotlib.axes.Axes`
The inset axes.
loc1, loc2 : {1, 2, 3, 4}
Corners to use for connecting the inset axes and the area in the
parent axes.
**kwargs
Patch properties for the lines and box drawn:
%(Patch)s
Returns
-------
pp : `matplotlib.patches.Patch`
The patch drawn to represent the area of the inset axes.
p1, p2 : `matplotlib.patches.Patch`
The patches connecting two corners of the inset axes and its area.
"""
rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData)
if 'fill' in kwargs:
pp = BboxPatch(rect, **kwargs)
else:
fill = ('fc' in kwargs) or ('facecolor' in kwargs) or ('color' in kwargs)
pp = BboxPatch(rect, fill=fill, **kwargs)
parent_axes.add_patch(pp)
p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1, **kwargs)
inset_axes.add_patch(p1)
p1.set_clip_on(False)
p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2, **kwargs)
inset_axes.add_patch(p2)
p2.set_clip_on(False)
return pp, p1, p2
@@ -0,0 +1,144 @@
import matplotlib.axes as maxes
from matplotlib.artist import Artist
from matplotlib.axis import XAxis, YAxis
class SimpleChainedObjects(object):
def __init__(self, objects):
self._objects = objects
def __getattr__(self, k):
_a = SimpleChainedObjects([getattr(a, k) for a in self._objects])
return _a
def __call__(self, *kl, **kwargs):
for m in self._objects:
m(*kl, **kwargs)
class Axes(maxes.Axes):
class AxisDict(dict):
def __init__(self, axes):
self.axes = axes
super().__init__()
def __getitem__(self, k):
if isinstance(k, tuple):
r = SimpleChainedObjects(
[super(Axes.AxisDict, self).__getitem__(k1) for k1 in k])
# super() within a list comprehension needs explicit args
return r
elif isinstance(k, slice):
if k.start is None and k.stop is None and k.step is None:
return SimpleChainedObjects(list(self.values()))
else:
raise ValueError("Unsupported slice")
else:
return dict.__getitem__(self, k)
def __call__(self, *v, **kwargs):
return maxes.Axes.axis(self.axes, *v, **kwargs)
def _init_axis_artists(self, axes=None):
if axes is None:
axes = self
self._axislines = self.AxisDict(self)
self._axislines.update(
bottom=SimpleAxisArtist(self.xaxis, 1, self.spines["bottom"]),
top=SimpleAxisArtist(self.xaxis, 2, self.spines["top"]),
left=SimpleAxisArtist(self.yaxis, 1, self.spines["left"]),
right=SimpleAxisArtist(self.yaxis, 2, self.spines["right"]))
def _get_axislines(self):
return self._axislines
axis = property(_get_axislines)
def cla(self):
super().cla()
self._init_axis_artists()
class SimpleAxisArtist(Artist):
def __init__(self, axis, axisnum, spine):
self._axis = axis
self._axisnum = axisnum
self.line = spine
if isinstance(axis, XAxis):
self._axis_direction = ["bottom", "top"][axisnum-1]
elif isinstance(axis, YAxis):
self._axis_direction = ["left", "right"][axisnum-1]
else:
raise ValueError("axis must be instance of XAxis or YAxis : %s is provided" % (axis,))
Artist.__init__(self)
def _get_major_ticks(self):
tickline = "tick%dline" % self._axisnum
return SimpleChainedObjects([getattr(tick, tickline)
for tick in self._axis.get_major_ticks()])
def _get_major_ticklabels(self):
label = "label%d" % self._axisnum
return SimpleChainedObjects([getattr(tick, label)
for tick in self._axis.get_major_ticks()])
def _get_label(self):
return self._axis.label
major_ticks = property(_get_major_ticks)
major_ticklabels = property(_get_major_ticklabels)
label = property(_get_label)
def set_visible(self, b):
self.toggle(all=b)
self.line.set_visible(b)
self._axis.set_visible(True)
Artist.set_visible(self, b)
def set_label(self, txt):
self._axis.set_label_text(txt)
def toggle(self, all=None, ticks=None, ticklabels=None, label=None):
if all:
_ticks, _ticklabels, _label = True, True, True
elif all is not None:
_ticks, _ticklabels, _label = False, False, False
else:
_ticks, _ticklabels, _label = None, None, None
if ticks is not None:
_ticks = ticks
if ticklabels is not None:
_ticklabels = ticklabels
if label is not None:
_label = label
tickOn = "tick%dOn" % self._axisnum
labelOn = "label%dOn" % self._axisnum
if _ticks is not None:
tickparam = {tickOn: _ticks}
self._axis.set_tick_params(**tickparam)
if _ticklabels is not None:
tickparam = {labelOn: _ticklabels}
self._axis.set_tick_params(**tickparam)
if _label is not None:
pos = self._axis.get_label_position()
if (pos == self._axis_direction) and not _label:
self._axis.label.set_visible(False)
elif _label:
self._axis.label.set_visible(True)
self._axis.set_label_position(self._axis_direction)
if __name__ == '__main__':
import matplotlib.pyplot as plt
fig = plt.figure()
ax = Axes(fig, [0.1, 0.1, 0.8, 0.8])
fig.add_axes(ax)
ax.cla()
@@ -0,0 +1,407 @@
import functools
from matplotlib import (
artist as martist, collections as mcoll, transforms as mtransforms,
rcParams)
from matplotlib.axes import subplot_class_factory
from matplotlib.transforms import Bbox
from .mpl_axes import Axes
import numpy as np
class ParasiteAxesBase:
def get_images_artists(self):
artists = {a for a in self.get_children() if a.get_visible()}
images = {a for a in self.images if a.get_visible()}
return list(images), list(artists - images)
def __init__(self, parent_axes, **kwargs):
self._parent_axes = parent_axes
kwargs["frameon"] = False
super().__init__(parent_axes.figure, parent_axes._position, **kwargs)
def cla(self):
super().cla()
martist.setp(self.get_children(), visible=False)
self._get_lines = self._parent_axes._get_lines
# In mpl's Axes, zorders of x- and y-axis are originally set
# within Axes.draw().
if self._axisbelow:
self.xaxis.set_zorder(0.5)
self.yaxis.set_zorder(0.5)
else:
self.xaxis.set_zorder(2.5)
self.yaxis.set_zorder(2.5)
@functools.lru_cache(None)
def parasite_axes_class_factory(axes_class=None):
if axes_class is None:
axes_class = Axes
return type("%sParasite" % axes_class.__name__,
(ParasiteAxesBase, axes_class), {})
ParasiteAxes = parasite_axes_class_factory()
class ParasiteAxesAuxTransBase:
def __init__(self, parent_axes, aux_transform, viewlim_mode=None,
**kwargs):
self.transAux = aux_transform
self.set_viewlim_mode(viewlim_mode)
super().__init__(parent_axes, **kwargs)
def _set_lim_and_transforms(self):
self.transAxes = self._parent_axes.transAxes
self.transData = \
self.transAux + \
self._parent_axes.transData
self._xaxis_transform = mtransforms.blended_transform_factory(
self.transData, self.transAxes)
self._yaxis_transform = mtransforms.blended_transform_factory(
self.transAxes, self.transData)
def set_viewlim_mode(self, mode):
if mode not in [None, "equal", "transform"]:
raise ValueError("Unknown mode: %s" % (mode,))
else:
self._viewlim_mode = mode
def get_viewlim_mode(self):
return self._viewlim_mode
def update_viewlim(self):
viewlim = self._parent_axes.viewLim.frozen()
mode = self.get_viewlim_mode()
if mode is None:
pass
elif mode == "equal":
self.axes.viewLim.set(viewlim)
elif mode == "transform":
self.axes.viewLim.set(
viewlim.transformed(self.transAux.inverted()))
else:
raise ValueError("Unknown mode: %s" % (self._viewlim_mode,))
def _pcolor(self, super_pcolor, *XYC, **kwargs):
if len(XYC) == 1:
C = XYC[0]
ny, nx = C.shape
gx = np.arange(-0.5, nx)
gy = np.arange(-0.5, ny)
X, Y = np.meshgrid(gx, gy)
else:
X, Y, C = XYC
if "transform" in kwargs:
mesh = super_pcolor(X, Y, C, **kwargs)
else:
orig_shape = X.shape
xyt = np.column_stack([X.flat, Y.flat])
wxy = self.transAux.transform(xyt)
gx = wxy[:, 0].reshape(orig_shape)
gy = wxy[:, 1].reshape(orig_shape)
mesh = super_pcolor(gx, gy, C, **kwargs)
mesh.set_transform(self._parent_axes.transData)
return mesh
def pcolormesh(self, *XYC, **kwargs):
return self._pcolor(super().pcolormesh, *XYC, **kwargs)
def pcolor(self, *XYC, **kwargs):
return self._pcolor(super().pcolor, *XYC, **kwargs)
def _contour(self, super_contour, *XYCL, **kwargs):
if len(XYCL) <= 2:
C = XYCL[0]
ny, nx = C.shape
gx = np.arange(0., nx)
gy = np.arange(0., ny)
X, Y = np.meshgrid(gx, gy)
CL = XYCL
else:
X, Y = XYCL[:2]
CL = XYCL[2:]
if "transform" in kwargs:
cont = super_contour(X, Y, *CL, **kwargs)
else:
orig_shape = X.shape
xyt = np.column_stack([X.flat, Y.flat])
wxy = self.transAux.transform(xyt)
gx = wxy[:, 0].reshape(orig_shape)
gy = wxy[:, 1].reshape(orig_shape)
cont = super_contour(gx, gy, *CL, **kwargs)
for c in cont.collections:
c.set_transform(self._parent_axes.transData)
return cont
def contour(self, *XYCL, **kwargs):
return self._contour(super().contour, *XYCL, **kwargs)
def contourf(self, *XYCL, **kwargs):
return self._contour(super().contourf, *XYCL, **kwargs)
def apply_aspect(self, position=None):
self.update_viewlim()
super().apply_aspect()
@functools.lru_cache(None)
def parasite_axes_auxtrans_class_factory(axes_class=None):
if axes_class is None:
parasite_axes_class = ParasiteAxes
elif not issubclass(axes_class, ParasiteAxesBase):
parasite_axes_class = parasite_axes_class_factory(axes_class)
else:
parasite_axes_class = axes_class
return type("%sParasiteAuxTrans" % parasite_axes_class.__name__,
(ParasiteAxesAuxTransBase, parasite_axes_class),
{'name': 'parasite_axes'})
ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(
axes_class=ParasiteAxes)
class HostAxesBase:
def __init__(self, *args, **kwargs):
self.parasites = []
super().__init__(*args, **kwargs)
def get_aux_axes(self, tr, viewlim_mode="equal", axes_class=None):
parasite_axes_class = parasite_axes_auxtrans_class_factory(axes_class)
ax2 = parasite_axes_class(self, tr, viewlim_mode)
# note that ax2.transData == tr + ax1.transData
# Anthing you draw in ax2 will match the ticks and grids of ax1.
self.parasites.append(ax2)
ax2._remove_method = self.parasites.remove
return ax2
def _get_legend_handles(self, legend_handler_map=None):
all_handles = super()._get_legend_handles()
for ax in self.parasites:
all_handles.extend(ax._get_legend_handles(legend_handler_map))
return all_handles
def draw(self, renderer):
orig_artists = list(self.artists)
orig_images = list(self.images)
if hasattr(self, "get_axes_locator"):
locator = self.get_axes_locator()
if locator:
pos = locator(self, renderer)
self.set_position(pos, which="active")
self.apply_aspect(pos)
else:
self.apply_aspect()
else:
self.apply_aspect()
rect = self.get_position()
for ax in self.parasites:
ax.apply_aspect(rect)
images, artists = ax.get_images_artists()
self.images.extend(images)
self.artists.extend(artists)
super().draw(renderer)
self.artists = orig_artists
self.images = orig_images
def cla(self):
for ax in self.parasites:
ax.cla()
super().cla()
def twinx(self, axes_class=None):
"""
create a twin of Axes for generating a plot with a sharex
x-axis but independent y axis. The y-axis of self will have
ticks on left and the returned axes will have ticks on the
right
"""
if axes_class is None:
axes_class = self._get_base_axes()
parasite_axes_class = parasite_axes_class_factory(axes_class)
ax2 = parasite_axes_class(self, sharex=self, frameon=False)
self.parasites.append(ax2)
ax2._remove_method = self._remove_twinx
self.axis["right"].set_visible(False)
ax2.axis["right"].set_visible(True)
ax2.axis["left", "top", "bottom"].set_visible(False)
return ax2
def _remove_twinx(self, ax):
self.parasites.remove(ax)
self.axis["right"].set_visible(True)
self.axis["right"].toggle(ticklabels=False, label=False)
def twiny(self, axes_class=None):
"""
create a twin of Axes for generating a plot with a shared
y-axis but independent x axis. The x-axis of self will have
ticks on bottom and the returned axes will have ticks on the
top
"""
if axes_class is None:
axes_class = self._get_base_axes()
parasite_axes_class = parasite_axes_class_factory(axes_class)
ax2 = parasite_axes_class(self, sharey=self, frameon=False)
self.parasites.append(ax2)
ax2._remove_method = self._remove_twiny
self.axis["top"].set_visible(False)
ax2.axis["top"].set_visible(True)
ax2.axis["left", "right", "bottom"].set_visible(False)
return ax2
def _remove_twiny(self, ax):
self.parasites.remove(ax)
self.axis["top"].set_visible(True)
self.axis["top"].toggle(ticklabels=False, label=False)
def twin(self, aux_trans=None, axes_class=None):
"""
create a twin of Axes for generating a plot with a sharex
x-axis but independent y axis. The y-axis of self will have
ticks on left and the returned axes will have ticks on the
right
"""
if axes_class is None:
axes_class = self._get_base_axes()
parasite_axes_auxtrans_class = \
parasite_axes_auxtrans_class_factory(axes_class)
if aux_trans is None:
ax2 = parasite_axes_auxtrans_class(
self, mtransforms.IdentityTransform(), viewlim_mode="equal")
else:
ax2 = parasite_axes_auxtrans_class(
self, aux_trans, viewlim_mode="transform")
self.parasites.append(ax2)
ax2._remove_method = self.parasites.remove
self.axis["top", "right"].set_visible(False)
ax2.axis["top", "right"].set_visible(True)
ax2.axis["left", "bottom"].set_visible(False)
def _remove_method(h):
self.parasites.remove(h)
self.axis["top", "right"].set_visible(True)
self.axis["top", "right"].toggle(ticklabels=False, label=False)
ax2._remove_method = _remove_method
return ax2
def get_tightbbox(self, renderer, call_axes_locator=True,
bbox_extra_artists=None):
bbs = [ax.get_tightbbox(renderer, call_axes_locator=call_axes_locator)
for ax in self.parasites]
bbs.append(super().get_tightbbox(renderer,
call_axes_locator=call_axes_locator,
bbox_extra_artists=bbox_extra_artists))
return Bbox.union([b for b in bbs if b.width != 0 or b.height != 0])
@functools.lru_cache(None)
def host_axes_class_factory(axes_class=None):
if axes_class is None:
axes_class = Axes
def _get_base_axes(self):
return axes_class
return type("%sHostAxes" % axes_class.__name__,
(HostAxesBase, axes_class),
{'_get_base_axes': _get_base_axes})
def host_subplot_class_factory(axes_class):
host_axes_class = host_axes_class_factory(axes_class=axes_class)
subplot_host_class = subplot_class_factory(host_axes_class)
return subplot_host_class
HostAxes = host_axes_class_factory(axes_class=Axes)
SubplotHost = subplot_class_factory(HostAxes)
def host_axes(*args, axes_class=None, figure=None, **kwargs):
"""
Create axes that can act as a hosts to parasitic axes.
Parameters
----------
figure : `matplotlib.figure.Figure`
Figure to which the axes will be added. Defaults to the current figure
`pyplot.gcf()`.
*args, **kwargs :
Will be passed on to the underlying ``Axes`` object creation.
"""
import matplotlib.pyplot as plt
host_axes_class = host_axes_class_factory(axes_class)
if figure is None:
figure = plt.gcf()
ax = host_axes_class(figure, *args, **kwargs)
figure.add_axes(ax)
plt.draw_if_interactive()
return ax
def host_subplot(*args, axes_class=None, figure=None, **kwargs):
"""
Create a subplot that can act as a host to parasitic axes.
Parameters
----------
figure : `matplotlib.figure.Figure`
Figure to which the subplot will be added. Defaults to the current
figure `pyplot.gcf()`.
*args, **kwargs :
Will be passed on to the underlying ``Axes`` object creation.
"""
import matplotlib.pyplot as plt
host_subplot_class = host_subplot_class_factory(axes_class)
if figure is None:
figure = plt.gcf()
ax = host_subplot_class(figure, *args, **kwargs)
figure.add_subplot(ax)
plt.draw_if_interactive()
return ax
@@ -0,0 +1,21 @@
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(axes_class=ParasiteAxes)
HostAxes = host_axes_class_factory(axes_class=Axes)
SubplotHost = subplot_class_factory(HostAxes)
@@ -0,0 +1,411 @@
import numpy as np
import math
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 = np.concatenate([second_limits_,
minute_limits_,
degree_limits_])
degree_steps = np.concatenate([minsec_steps_,
minsec_steps_,
degree_steps_])
degree_factors = np.concatenate([second_factors,
minute_factors,
degree_factors])
n = degree_limits.searchsorted(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 = np.concatenate([second_limits_,
minute_limits_,
hour_limits_])
hour_steps = np.concatenate([minsec_steps_,
minsec_steps_,
hour_steps_])
hour_factors = np.concatenate([second_factors,
minute_factors,
hour_factors])
n = hour_limits.searchsorted(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
f1, f2, fstep = v1*factor, v2*factor, step/factor
levs = np.arange(np.floor(f1/step), np.ceil(f2/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(object):
def __init__(self, den, include_last=True):
self.den = den
self._include_last = include_last
@property
def nbins(self):
return self.den
@nbins.setter
def nbins(self, v):
self.den = v
def set_params(self, nbins=None):
if nbins is not None:
self.den = int(nbins)
class LocatorHMS(LocatorBase):
def __call__(self, v1, v2):
return select_step24(v1, v2, self.den, self._include_last)
class LocatorHM(LocatorBase):
def __call__(self, v1, v2):
return select_step24(v1, v2, self.den, self._include_last,
threshold_factor=60)
class LocatorH(LocatorBase):
def __call__(self, v1, v2):
return select_step24(v1, v2, self.den, self._include_last,
threshold_factor=1)
class LocatorDMS(LocatorBase):
def __call__(self, v1, v2):
return select_step360(v1, v2, self.den, self._include_last)
class LocatorDM(LocatorBase):
def __call__(self, v1, v2):
return select_step360(v1, v2, self.den, self._include_last,
threshold_factor=60)
class LocatorD(LocatorBase):
def __call__(self, v1, v2):
return select_step360(v1, v2, self.den, self._include_last,
threshold_factor=1)
class FormatterDMS(object):
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 = [[-1, 1][v>0] for v in values] #not py24 compliant
values = np.asarray(values)
ss = np.where(values>0, 1, -1)
sign_map = {(-1, True):"-"}
signs = [sign_map.get((s, v!=0), "") for s, v in zip(ss, 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 #l_s
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 FormatterDMS.__call__(self, direction, factor, np.asarray(values)/15.)
class ExtremeFinderCycle(ExtremeFinderSimple):
"""
When there is a cycle, e.g., longitude goes from 0-360.
"""
def __init__(self,
nx, ny,
lon_cycle = 360.,
lat_cycle = None,
lon_minmax = None,
lat_minmax = (-90, 90)
):
#self.transform_xy = transform_xy
#self.inv_transform_xy = inv_transform_xy
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):
"""
get extreme values.
x1, y1, x2, y2 in image coordinates (0-based)
nx, ny : number of divisions in each axis
"""
x_, y_ = np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny)
x, y = np.meshgrid(x_, y_)
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._adjust_extremes(lon_min, lon_max, lat_min, lat_max)
return lon_min, lon_max, lat_min, lat_max
def _adjust_extremes(self, lon_min, lon_max, lat_min, lat_max):
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,19 @@
from matplotlib import cbook
from mpl_toolkits.axes_grid1.axes_divider import (
Divider, AxesLocator, SubplotDivider, AxesDivider, locatable_axes_factory,
make_axes_locatable)
from mpl_toolkits.axisartist.axislines import Axes as _Axes
@cbook.deprecated('3.0',
alternative='mpl_toolkits.axisartist.axislines.Axes')
class Axes(_Axes):
pass
@cbook.deprecated('3.0',
alternative='mpl_toolkits.axisartist.axislines.Axes')
class LocatableAxes(_Axes):
pass
@@ -0,0 +1,26 @@
import mpl_toolkits.axes_grid1.axes_grid as axes_grid_orig
from .axislines import Axes
class CbarAxes(axes_grid_orig.CbarAxesBase, Axes):
def __init__(self, *args, orientation, **kwargs):
self.orientation = orientation
self._default_label_on = False
self.locator = None
super().__init__(*args, **kwargs)
def cla(self):
super().cla()
self._config_axes()
class Grid(axes_grid_orig.Grid):
_defaultAxesClass = Axes
class ImageGrid(axes_grid_orig.ImageGrid):
_defaultAxesClass = Axes
_defaultCbarAxesClass = CbarAxes
AxesGrid = ImageGrid
@@ -0,0 +1,8 @@
from mpl_toolkits.axes_grid1.axes_rgb import (
make_rgb_axes, imshow_rgb, RGBAxesBase)
from .axislines import Axes
class RGBAxes(RGBAxesBase):
_defaultAxesClass = Axes
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,163 @@
from matplotlib.patches import _Style, FancyArrowPatch
from matplotlib.transforms import IdentityTransform
from matplotlib.path import Path
import numpy as np
class _FancyAxislineStyle(object):
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,
arrow_transmuter=None,
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()
extented_path = self._extend_path(path_in_disp,
mutation_size=mutation_size)
self._path_original = extented_path
FancyArrowPatch.draw(self, renderer)
class FilledArrow(SimpleArrow):
"""
The artist class that will be returned for SimpleArrow style.
"""
_ARROW_STYLE = "-|>"
class AxislineStyle(_Style):
"""
:class:`AxislineStyle` is 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 a mpl artist with following
methods implemented. ::
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(object):
# 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 mpl 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):
"""
*size*
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
@@ -0,0 +1,692 @@
"""
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 mpl'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 mpl, 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 Axis class of the
original mpl, thus most of tick-related command in the original mpl
won't work, although some effort has made to work with. 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 warnings
import numpy as np
from matplotlib import rcParams
import matplotlib.artist as martist
import matplotlib.axes as maxes
from matplotlib.path import Path
from matplotlib.transforms import Bbox
from .axisline_style import AxislineStyle
from .axis_artist import AxisArtist, GridlinesCollection
class AxisArtistHelper(object):
"""
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(object):
"""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
"""
self._loc = loc
if loc not in ["left", "right", "bottom", "top"]:
raise ValueError("%s" % 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):
"""
label reference position in transAxes.
get_label_transform() returns a transform of (transAxes+offset)
"""
loc = self._loc
pos, angle_tangent = dict(left=((0., 0.5), 90),
right=((1., 0.5), 90),
bottom=((0.5, 0.), 0),
top=((0.5, 1.), 0))[loc]
return pos, angle_tangent
# TICK
def get_tick_transform(self, axes):
trans_tick = [axes.get_xaxis_transform(),
axes.get_yaxis_transform()][self.nth_coord]
return trans_tick
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(object):
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()
major.formatter.set_locs(majorLocs)
majorLabels = [major.formatter(val, i) for i, val in enumerate(majorLocs)]
minor = self.axis.minor
minorLocs = minor.locator()
minor.formatter.set_locs(minorLocs)
minorLabels = [minor.formatter(val, i) for i, val in enumerate(minorLocs)]
trans_tick = self.get_tick_transform(axes)
tr2ax = trans_tick + axes.transAxes.inverted()
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 = tr2ax.transform_point(c)
#delta=0.00001
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
trans_passingthrough_point = axes.transData + axes.transAxes.inverted()
p = trans_passingthrough_point.transform_point([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):
"""
label reference position in transAxes.
get_label_transform() returns a transform of (transAxes+offset)
"""
loc = self._axis_direction
#angle = dict(left=0,
# right=0,
# bottom=.5*np.pi,
# top=.5*np.pi)[loc]
if self.nth_coord == 0:
angle = 0
else:
angle = 90
_verts = [0.5, 0.5]
fixed_coord = 1-self.nth_coord
trans_passingthrough_point = axes.transData + axes.transAxes.inverted()
p = trans_passingthrough_point.transform_point([self._value,
self._value])
_verts[fixed_coord] = p[fixed_coord]
if not (0. <= _verts[fixed_coord] <= 1.):
return None, None
else:
return _verts, angle
def get_tick_transform(self, axes):
return axes.transData
def get_tick_iterators(self, axes):
"""tick_loc, tick_angle, tick_label"""
loc = self._axis_direction
if loc in ["bottom", "top"]:
angle_normal, angle_tangent = 90, 0
else:
angle_normal, angle_tangent = 0, 90
if self.nth_coord == 0:
angle_normal, angle_tangent = 90, 0
else:
angle_normal, angle_tangent = 0, 90
# angle = 90 - 90 * self.nth_coord
major = self.axis.major
majorLocs = major.locator()
major.formatter.set_locs(majorLocs)
majorLabels = [major.formatter(val, i) for i, val in enumerate(majorLocs)]
minor = self.axis.minor
minorLocs = minor.locator()
minor.formatter.set_locs(minorLocs)
minorLabels = [minor.formatter(val, i) for i, val in enumerate(minorLocs)]
tr2ax = axes.transData + axes.transAxes.inverted()
def _f(locs, labels):
for x, l in zip(locs, labels):
c = [self._value, self._value]
c[self.nth_coord] = x
c1, c2 = tr2ax.transform_point(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(object):
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:
warnings.warn("'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:
warnings.warn(
"'new_floating_axis' explicitly requires the axes keyword.")
axes = self.axes
passthrough_point = (value, value)
transform = axes.transData
_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 SimpleChainedObjects(object):
def __init__(self, objects):
self._objects = objects
def __getattr__(self, k):
_a = SimpleChainedObjects([getattr(a, k) for a in self._objects])
return _a
def __call__(self, *kl, **kwargs):
for m in self._objects:
m(*kl, **kwargs)
class Axes(maxes.Axes):
class AxisDict(dict):
def __init__(self, axes):
self.axes = axes
super().__init__()
def __getitem__(self, k):
if isinstance(k, tuple):
return SimpleChainedObjects(
[dict.__getitem__(self, k1) for k1 in k])
elif isinstance(k, slice):
if k == slice(None):
return SimpleChainedObjects(list(self.values()))
else:
raise ValueError("Unsupported slice")
else:
return dict.__getitem__(self, k)
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 = self.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:
if self.axes.xaxis._gridOnMinor or self.axes.xaxis._gridOnMajor or \
self.axes.yaxis._gridOnMinor or self.axes.yaxis._gridOnMajor:
b=True
else:
b=False
self.gridlines.set_which(which)
self.gridlines.set_axis(axis)
self.gridlines.set_visible(b)
if len(kwargs):
martist.setp(self.gridlines, **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)
@@ -0,0 +1,115 @@
import numpy as np
from math import degrees
import math
import warnings
def atan2(dy, dx):
if dx == 0 and dy == 0:
warnings.warn("dx and dy is 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!=0):
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)
# lx1, ly1, c_right_ = clip([xline], [yline], x1, clip="right")
# lx2, ly2, c_left_ = clip(lx1, ly1, x0, clip="left")
# ly3, lx3, c_top_ = clip(ly2, lx2, y1, clip="right")
# ly4, lx4, c_bottom_ = clip(ly3, lx3, y0, clip="left")
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]
@@ -0,0 +1,522 @@
"""
An experimental support for curvilinear grid.
"""
import functools
# TODO :
# see if tick_iterator method can be simplified by reusing the parent method.
import numpy as np
from matplotlib.transforms import Affine2D, IdentityTransform
from . import grid_helper_curvelinear
from .axislines import AxisArtistHelper, GridHelperBase
from .axis_artist import AxisArtist
from .grid_finder import GridFinder
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) # return v= 0 , nth=1, extremes of the other coordinate.
super().__init__(grid_helper, nth_coord, value, axis_direction=side)
#self.grid_helper = grid_helper
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_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, dyy = 0., abs(extremes[2]-extremes[3])/1000.
elif self.nth_coord == 1:
xx0 = (extremes[0]+extremes[1])/2.
yy0 = self.value
dxx, dyy = abs(extremes[0]-extremes[1])/1000., 0.
grid_finder = self.grid_helper.grid_finder
xx1, yy1 = grid_finder.transform_xy([xx0], [yy0])
trans_passingthrough_point = axes.transData + axes.transAxes.inverted()
p = trans_passingthrough_point.transform_point([xx1[0], yy1[0]])
if 0 <= p[0] <= 1 and 0 <= p[1] <= 1:
xx1c, yy1c = axes.transData.transform_point([xx1[0], yy1[0]])
xx2, yy2 = grid_finder.transform_xy([xx0+dxx], [yy0+dyy])
xx2c, yy2c = axes.transData.transform_point([xx2[0], yy2[0]])
return (xx1c, yy1c), np.arctan2(yy2c-yy1c, xx2c-xx1c)/np.pi*180.
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"]
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:])
if self.nth_coord == 0:
mask = (ymin <= yy0) & (yy0 <= ymax)
yy0 = yy0[mask]
elif self.nth_coord == 1:
mask = (xmin <= xx0) & (xx0 <= xmax)
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.empty_like(yy0)
xx0.fill(self.value)
#yy0_ = yy0.copy()
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:
yy0 = np.empty_like(xx0)
yy0.fill(self.value)
#xx0_ = xx0.copy()
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 where dd1 is not defined
dd[mm] = dd2[mm] + np.pi / 2
#dd += np.pi
#dd = np.arctan2(xx2-xx1, angle_tangent-yy1)
trans_tick = self.get_tick_transform(axes)
tr2ax = trans_tick + axes.transAxes.inverted()
for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels):
c2 = tr2ax.transform_point((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)
from matplotlib.path import Path
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]))
from .grid_finder import ExtremeFinderSimple
class ExtremeFinderFixed(ExtremeFinderSimple):
def __init__(self, extremes):
self._extremes = extremes
def __call__(self, transform_xy, x1, y1, x2, y2):
"""
get extreme values.
x1, y1, x2, y2 in image coordinates (0-based)
nx, ny : number of division in each axis
"""
#lon_min, lon_max, lat_min, lat_max = self._extremes
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):
"""
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)*
"""
self._old_values = None
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 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 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
_helper = FixedAxisArtistHelper(self, loc,
nth_coord_ticks=nth_coord)
axisline = AxisArtist(axes, _helper, axis_direction=axis_direction)
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):
#self.grid_info = self.grid_finder.get_grid_info(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_values0 = lon_values[(lon_min<lon_values) & (lon_values<lon_max)]
lat_values0 = lat_values[(lat_min<lat_values) & (lat_values<lat_max)]
lon_lines, lat_lines = grid_finder._get_raw_grid_lines(lon_values0,
lat_values0,
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(extremes[:2],
extremes[2:],
*extremes)
#lon_min, lon_max,
# lat_min, lat_max)
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"]:
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_boundary(self):
"""
return Nx2 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, yy1 = np.empty_like(xx), np.empty_like(xx)
yy0.fill(y0)
yy1.fill(y1)
yy = np.linspace(y0, y1, 100)
xx0, xx1 = np.empty_like(yy), np.empty_like(yy)
xx0.fill(x0)
xx1.fill(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(object):
def __init__(self, *kl, **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, *kl, **kwargs)
self.set_aspect(1.)
self.adjust_axes_lim()
def _gen_axes_patch(self):
"""
Returns the patch used to draw the background of the axes. It
is also used as the clipping path for any data elements on the
axes.
In the standard axes, this is a rectangle, but in other
projections it may not be.
.. note::
Intended to be overridden by new projection types.
"""
import matplotlib.patches as mpatches
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):
#t = self.get_boundary()
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})
from .axislines import Axes
from mpl_toolkits.axes_grid1.parasite_axes import host_axes_class_factory
FloatingAxes = floatingaxes_class_factory(host_axes_class_factory(Axes))
import matplotlib.axes as maxes
FloatingSubplot = maxes.subplot_class_factory(FloatingAxes)
@@ -0,0 +1,332 @@
import numpy as np
from matplotlib.transforms import Bbox
from . import clip_path
clip_line_to_rect = clip_path.clip_line_to_rect
import matplotlib.ticker as mticker
from matplotlib.transforms import Transform
# extremes finder
class ExtremeFinderSimple(object):
def __init__(self, nx, ny):
self.nx, self.ny = nx, ny
def __call__(self, transform_xy, x1, y1, x2, y2):
"""
get extreme values.
x1, y1, x2, y2 in image coordinates (0-based)
nx, ny : number of division in each axis
"""
x_, y_ = np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny)
x, y = np.meshgrid(x_, y_)
lon, lat = transform_xy(np.ravel(x), np.ravel(y))
lon_min, lon_max = lon.min(), lon.max()
lat_min, lat_max = lat.min(), lat.max()
return self._add_pad(lon_min, lon_max, lat_min, lat_max)
def _add_pad(self, lon_min, lon_max, lat_min, lat_max):
""" a small amount of padding is added because the current
clipping algorithms seems to fail when the gridline ends at
the bbox boundary.
"""
dlon = (lon_max - lon_min) / self.nx
dlat = (lat_max - lat_min) / self.ny
lon_min, lon_max = lon_min - dlon, lon_max + dlon
lat_min, lat_max = lat_min - dlat, lat_max + dlat
return lon_min, lon_max, lat_min, lat_max
class GridFinderBase(object):
def __init__(self,
extreme_finder,
grid_locator1,
grid_locator2,
tick_formatter1=None,
tick_formatter2=None):
"""
the transData of the axes to the world coordinate.
locator1, locator2 : grid locator for 1st and 2nd axis.
Derived must define "transform_xy, inv_transform_xy"
(may use update_transform)
"""
super().__init__()
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
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)
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 = 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 = {}
grid_info["extremes"] = extremes
grid_info["lon_lines"] = lon_lines
grid_info["lat_lines"] = lat_lines
grid_info["lon"] = self._clip_grid_lines_and_find_ticks(lon_lines,
lon_values,
lon_levs,
bb)
grid_info["lat"] = self._clip_grid_lines_and_find_ticks(lat_lines,
lat_values,
lat_levs,
bb)
tck_labels = grid_info["lon"]["tick_labels"] = dict()
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"] = dict()
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.zeros_like(lats_i) + lon, lats_i)
for lon in lon_values]
lat_lines = [self.transform_xy(lons_i, np.zeros_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)
class GridFinder(GridFinderBase):
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()
super().__init__(
extreme_finder,
grid_locator1,
grid_locator2,
tick_formatter1,
tick_formatter2)
self.update_transform(transform)
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 = None
def __call__(self, v1, v2):
if self._factor is not None:
self.set_bounds(v1*self._factor, v2*self._factor)
locs = mticker.MaxNLocator.__call__(self)
return np.array(locs), len(locs), self._factor
else:
self.set_bounds(v1, v2)
locs = mticker.MaxNLocator.__call__(self)
return np.array(locs), len(locs), None
def set_factor(self, f):
self._factor = f
class FixedLocator(object):
def __init__(self, locs):
self._locs = locs
self._factor = None
def __call__(self, v1, v2):
if self._factor is None:
v1, v2 = sorted([v1, v2])
else:
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
def set_factor(self, f):
self._factor = f
# Tick Formatter
class FormatterPrettyPrint(object):
def __init__(self, useMathText=True):
self._fmt = mticker.ScalarFormatter(
useMathText=useMathText, useOffset=False)
self._fmt.create_dummy_axis()
self._ignore_factor = True
def __call__(self, direction, factor, values):
if not self._ignore_factor:
if factor is None:
factor = 1.
values = [v/factor for v in values]
#values = [v for v in values]
self._fmt.set_locs(values)
return [self._fmt(v) for v in values]
class DictFormatter(object):
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)
r = [self._format_dict.get(k, v) for k, v in zip(values,
fallback_strings)]
return r
@@ -0,0 +1,464 @@
"""
An experimental support for curvilinear grid.
"""
from itertools import chain
from .grid_finder import GridFinder
from .axislines import AxisArtistHelper, GridHelperBase
from .axis_artist import AxisArtist
from matplotlib.transforms import Affine2D, IdentityTransform
import numpy as np
from matplotlib.path import Path
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)
#ti2 = g.get_tick_iterator(1-self.nth_coord_ticks, self.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 = None, None
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):
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)
extremes = list(extremes)
e1, e2 = self._extremes # ranges of other coordinates
if self.nth_coord == 0:
if e1 is not None:
extremes[2] = max(e1, extremes[2])
if e2 is not None:
extremes[3] = min(e2, extremes[3])
elif self.nth_coord == 1:
if e1 is not None:
extremes[0] = max(e1, extremes[0])
if e2 is not None:
extremes[1] = min(e2, extremes[1])
grid_info = dict()
lon_min, lon_max, lat_min, lat_max = extremes
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"] = 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)
grid_finder = self.grid_helper.grid_finder
#e1, e2 = self._extremes # ranges of other coordinates
if self.nth_coord == 0:
xx0 = np.linspace(self.value, self.value, self._line_num_points)
yy0 = np.linspace(extremes[2], extremes[3], self._line_num_points)
xx, yy = grid_finder.transform_xy(xx0, yy0)
elif self.nth_coord == 1:
xx0 = np.linspace(extremes[0], extremes[1], self._line_num_points)
yy0 = np.linspace(self.value, self.value, self._line_num_points)
xx, yy = grid_finder.transform_xy(xx0, yy0)
grid_info["line_xy"] = xx, yy
self.grid_info = grid_info
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, dyy = 0., abs(extremes[2]-extremes[3])/1000.
elif self.nth_coord == 1:
xx0 = (extremes[0]+extremes[1])/2.
yy0 = self.value
dxx, dyy = abs(extremes[0]-extremes[1])/1000., 0.
grid_finder = self.grid_helper.grid_finder
xx1, yy1 = grid_finder.transform_xy([xx0], [yy0])
trans_passingthrough_point = axes.transData + axes.transAxes.inverted()
p = trans_passingthrough_point.transform_point([xx1[0], yy1[0]])
if 0 <= p[0] <= 1 and 0 <= p[1] <= 1:
xx1c, yy1c = axes.transData.transform_point([xx1[0], yy1[0]])
xx2, yy2 = grid_finder.transform_xy([xx0+dxx], [yy0+dyy])
xx2c, yy2c = axes.transData.transform_point([xx2[0], yy2[0]])
return (xx1c, yy1c), np.arctan2(yy2c-yy1c, xx2c-xx1c)/np.pi*180.
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)
if lat_factor is not None:
yy0 = lat_levs / lat_factor
dy = 0.01 / lat_factor
else:
yy0 = lat_levs
dy = 0.01
lon_levs, lon_n, lon_factor = self.grid_info["lon_info"]
lon_levs = np.asarray(lon_levs)
if lon_factor is not None:
xx0 = lon_levs / lon_factor
dx = 0.01 / lon_factor
else:
xx0 = lon_levs
dx = 0.01
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.empty_like(yy0)
xx0.fill(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.empty_like(xx0)
yy0.fill(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)==0.) & ((xx1b-xx1a)==0.) # mask where dd1 is not defined
dd[mm] = dd2[mm] + np.pi / 2
#dd = np.arctan2(yy2-yy1, xx2-xx1) # angle normal
#dd2 = np.arctan2(yy3-yy1, xx3-xx1) # angle tangent
#mm = ((yy2-yy1)==0.) & ((xx2-xx1)==0.) # mask where dd1 is not defined
#dd[mm] = dd2[mm] + np.pi / 2
#dd += np.pi
#dd = np.arctan2(xx2-xx1, angle_tangent-yy1)
trans_tick = self.get_tick_transform(axes)
tr2ax = trans_tick + axes.transAxes.inverted()
for x, y, d, d2, lab in zip(xx1, yy1, dd, dd2, labels):
c2 = tr2ax.transform_point((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._grid_params = dict()
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,
nth_coord_ticks=nth_coord,
)
axisline = AxisArtist(axes, _helper, axis_direction=axis_direction)
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)
#axisline.major_ticklabels.set_rotate_along_line(True)
#axisline.set_rotate_label_along_line(True)
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
def f():
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:
def f():
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, ""
return f()
@@ -0,0 +1,15 @@
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(axes_class=ParasiteAxes)
HostAxes = host_axes_class_factory(axes_class=Axes)
SubplotHost = subplot_class_factory(HostAxes)
@@ -0,0 +1 @@
from .axes3d import Axes3D
@@ -0,0 +1,781 @@
# 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, cbook, colors as mcolors, lines, text as mtext, path as mpath)
from matplotlib.collections import (
Collection, 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': euqivalent to (0, 1, 0)
- 'z': equivalent to (0, 0, 1)
- *None*: euqivalent 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 cbook.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]
if dx==0. and dy==0.:
# atan2 raises ValueError: math domain error on 0,0
angle = 0.
else:
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()
try:
# If *zs* is a list or array, then this will fail and
# just proceed to juggle_axes().
zs = np.full_like(xs, fill_value=float(zs))
except TypeError:
pass
self._verts3d = juggle_axes(xs, ys, zs, zdir)
self.stale = True
@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))
seg = []
codes = []
pathsegs = path.iter_segments(simplify=False, curves=False)
for (((x, y), code), z) in zip(pathsegs, zs):
seg.append((x, y, z))
codes.append(code)
seg3d = [juggle_axes(x, y, z, zdir) for (x, y, z) in seg]
return seg3d, 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_list = []
for path, pathz in zip(paths, zs):
segs, codes = path_to_3d_segment_with_codes(path, pathz, zdir)
segments.append(segs)
codes_list.append(codes)
return segments, codes_list
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 = np.asanyarray(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.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)
PathCollection.set_offsets(self, np.column_stack([vxs, vys]))
if vzs.size > 0 :
return min(vzs)
else :
return 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.
"""
def __init__(self, verts, *args, zsort=True, **kwargs):
"""
Create a Poly3DCollection.
*verts* should contain 3D coordinates.
Keyword arguments:
zsort, see set_zsort for options.
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):
"""
Sets the calculation method for the z-order.
Parameters
----------
zsort : bool or {'average', 'min', 'max'}
For 'average', 'min', 'max' the z-order is determined by applying
the function to the z-coordinates of the vertices in the viewer's
coordinate system. *True* is equivalent to 'average'.
"""
if zsort is True:
zsort = 'average'
if zsort is not False:
if zsort in self._zsort_functions:
zsortfunc = self._zsort_functions[zsort]
else:
return False
else:
zsortfunc = None
self._zsort = zsort
self._sort_zpos = None
self._zsortfunc = zsortfunc
self.stale = True
def get_vector(self, segments3d):
"""Optimize points for projection."""
si = 0
ei = 0
segis = []
points = []
for p in segments3d:
points.extend(p)
ei = si + len(p)
segis.append((si, ei))
si = ei
if len(segments3d):
xs, ys, zs = zip(*points)
else :
# We need this so that we can skip the bad unpacking from zip()
xs, ys, zs = [], [], []
ones = np.ones(len(xs))
self._vec = np.array([xs, ys, zs, ones])
self._segis = segis
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):
"""Sets 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(True)
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[si:ei], tys[si:ei], tzs[si:ei])
for si, ei in self._segis]
# 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)
# if required sort by depth (furthest drawn first)
if self._zsort:
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)
else:
raise ValueError("whoops")
segments_2d = [s for z, s, fc, ec, idx in z_segments_2d]
if self._codes3d is not None:
codes = [self._codes3d[idx] for z, s, fc, ec, idx in z_segments_2d]
PolyCollection.set_verts_and_codes(self, segments_2d, codes)
else:
PolyCollection.set_verts(self, segments_2d, self._closed)
self._facecolors2d = [fc for z, s, fc, ec, idx in z_segments_2d]
if len(self._edgecolors3d) == len(cface):
self._edgecolors2d = [ec for z, s, fc, ec, idx in z_segments_2d]
else:
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):
"""
Set the alpha transparencies of the collection.
Parameters
----------
alpha : float or None
"""
if alpha is not None:
try:
float(alpha)
except TypeError:
raise TypeError('alpha must be a float or None')
artist.Artist.set_alpha(self, alpha)
try:
self._facecolors = 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.
colors = get_colors(colors, len(zs))
if len(zs):
norm = Normalize(min(zs), max(zs))
sats = 1 - norm(zs) * 0.7
colors = [(c[0], c[1], c[2], c[3] * s) for c, s in zip(colors, sats)]
return colors
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,478 @@
# axis3d.py, original mplot3d version by John Porter
# Created: 23 Sep 2005
# Parts rewritten by Reinier Heeres <reinier@heeres.eu>
import copy
from matplotlib import (
artist, lines as mlines, axis as maxis, patches as mpatches, rcParams)
from . import art3d, proj3d
import numpy as np
def get_flip_min_max(coord, index, mins, maxs):
if coord[index] == mins[index]:
return maxs[index]
else:
return mins[index]
def move_from_center(coord, centers, deltas, axmask=(True, True, True)):
'''Return a coordinate that is moved by "deltas" away from the center.'''
coord = copy.copy(coord)
for i in range(3):
if not axmask[i]:
continue
if coord[i] < centers[i]:
coord[i] -= deltas[i]
else:
coord[i] += deltas[i]
return coord
def tick_update_position(tick, tickxs, tickys, labelpos):
'''Update tick line and label position and style.'''
for (label, on) in [(tick.label1, tick.label1On),
(tick.label2, tick.label2On)]:
if on:
label.set_position(labelpos)
tick.tick1On, tick.tick2On = True, 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):
# 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
# data and viewing intervals for this direction
self.d_interval = d_intervalx
self.v_interval = v_intervalx
# 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': rcParams['lines.linewidth'],
'color': 'k'},
'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': rcParams.get(
adir + 'tick.major.width',
rcParams['xtick.major.width']),
'color': rcParams.get(
adir + 'tick.color',
rcParams['xtick.color'])},
'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)
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_tick_positions(self):
majorLocs = self.major.locator()
self.major.formatter.set_locs(majorLocs)
majorLabels = [self.major.formatter(val, i)
for i, val in enumerate(majorLocs)]
return majorLabels, majorLocs
def get_major_ticks(self, numticks=None):
ticks = maxis.XAxis.get_major_ticks(self, numticks)
for t in ticks:
t.tick1line.set_transform(self.axes.transData)
t.tick2line.set_transform(self.axes.transData)
t.gridline.set_transform(self.axes.transData)
t.label1.set_transform(self.axes.transData)
t.label2.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):
minx, maxx, miny, maxy, minz, maxz = self.axes.get_w_lims()
if minx > maxx:
minx, maxx = maxx, minx
if miny > maxy:
miny, maxy = maxy, miny
if minz > maxz:
minz, maxz = maxz, minz
mins = np.array((minx, miny, minz))
maxs = np.array((maxx, maxy, maxz))
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')
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')
# code from XAxis
majorTicks = self.get_major_ticks()
majorLocs = self.major.locator()
info = self._axinfo
index = info['i']
# filter locations here so that no extra grid lines are drawn
locmin, locmax = self.get_view_interval()
if locmin > locmax:
locmin, locmax = locmax, locmin
# Rudimentary clipping
majorLocs = [loc for loc in majorLocs if
locmin <= loc <= locmax]
self.major.formatter.set_locs(majorLocs)
majorLabels = [self.major.formatter(val, i)
for i, val in enumerate(majorLocs)]
mins, maxs, centers, deltas, tc, highs = self._get_coord_info(renderer)
# Determine grid lines
minmax = np.where(highs, maxs, mins)
# Draw main axis line
juggled = info['juggled']
edgep1 = minmax.copy()
edgep1[juggled[0]] = get_flip_min_max(edgep1, juggled[0], mins, maxs)
edgep2 = edgep1.copy()
edgep2[juggled[1]] = get_flip_min_max(edgep2, juggled[1], mins, maxs)
pep = proj3d.proj_trans_points([edgep1, edgep2], renderer.M)
centpt = proj3d.proj_transform(
centers[0], centers[1], centers[2], renderer.M)
self.line.set_data((pep[0][0], pep[0][1]), (pep[1][0], pep[1][1]))
self.line.draw(renderer)
# Grid points where the planes meet
xyz0 = []
for val in majorLocs:
coord = minmax.copy()
coord[index] = val
xyz0.append(coord)
# Draw labels
peparray = np.asanyarray(pep)
# 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([peparray[0:2, 1]]) -
self.axes.transAxes.transform([peparray[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[0], lxyz[1], lxyz[2],
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 = copy.copy(outeredgep)
pos = move_from_center(pos, centers, labeldeltas, axmask)
olx, oly, olz = proj3d.proj_transform(
pos[0], pos[1], pos[2], 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']] <= peparray[info['tickdir'], outerindex],
# centpt[index] <= peparray[index, outerindex])
#
# Three-letters (e.g., TFT, FTT) are short-hand for the array of bools
# from the variable 'highs'.
# ---------------------------------------------------------------------
if centpt[info['tickdir']] > peparray[info['tickdir'], outerindex]:
# if FT and if highs has an even number of Trues
if (centpt[index] <= peparray[index, outerindex]
and len(highs.nonzero()[0]) % 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] > peparray[index, outerindex]
and len(highs.nonzero()[0]) % 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)
# Draw grid lines
if len(xyz0) > 0:
# Grid points at end of one plane
xyz1 = copy.deepcopy(xyz0)
newindex = (index + 1) % 3
newval = get_flip_min_max(xyz1[0], newindex, mins, maxs)
for i in range(len(majorLocs)):
xyz1[i][newindex] = newval
# Grid points at end of the other plane
xyz2 = copy.deepcopy(xyz0)
newindex = (index + 2) % 3
newval = get_flip_min_max(xyz2[0], newindex, mins, maxs)
for i in range(len(majorLocs)):
xyz2[i][newindex] = newval
lines = list(zip(xyz1, xyz0, xyz2))
if self.axes._draw_grid:
self.gridlines.set_segments(lines)
self.gridlines.set_color([info['grid']['color']] * len(lines))
self.gridlines.set_linewidth(
[info['grid']['linewidth']] * len(lines))
self.gridlines.set_linestyle(
[info['grid']['linestyle']] * len(lines))
self.gridlines.draw(renderer, project=True)
# Draw ticks
tickdir = info['tickdir']
tickdelta = deltas[tickdir]
if highs[tickdir]:
ticksign = 1
else:
ticksign = -1
for tick, loc, label in zip(majorTicks, majorLocs, majorLabels):
if tick is None:
continue
# Get tick line positions
pos = copy.copy(edgep1)
pos[index] = loc
pos[tickdir] = (
edgep1[tickdir]
+ info['tick']['outward_factor'] * ticksign * tickdelta)
x1, y1, z1 = proj3d.proj_transform(pos[0], pos[1], pos[2],
renderer.M)
pos[tickdir] = (
edgep1[tickdir]
- info['tick']['inward_factor'] * ticksign * tickdelta)
x2, y2, z2 = proj3d.proj_transform(pos[0], pos[1], pos[2],
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[0], pos[1], pos[2],
renderer.M)
tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly))
tick.tick1line.set_linewidth(info['tick']['linewidth'])
tick.tick1line.set_color(info['tick']['color'])
tick.set_label1(label)
tick.set_label2(label)
tick.draw(renderer)
renderer.close_group('axis3d')
self.stale = False
def get_view_interval(self):
"""return the Interval instance for this 3d axis view limits"""
return self.v_interval
def set_view_interval(self, vmin, vmax, ignore=False):
if ignore:
self.v_interval = vmin, vmax
else:
Vmin, Vmax = self.get_view_interval()
self.v_interval = min(vmin, Vmin), max(vmax, Vmax)
# TODO: Get this to work properly when mplot3d supports
# the transforms framework.
def get_tightbbox(self, renderer):
# Currently returns None so that Axis.get_tightbbox
# doesn't return junk info.
return None
# Use classes to look at different data limits
class XAxis(Axis):
def get_data_interval(self):
'return the Interval instance for this axis data limits'
return self.axes.xy_dataLim.intervalx
class YAxis(Axis):
def get_data_interval(self):
'return the Interval instance for this axis data limits'
return self.axes.xy_dataLim.intervaly
class ZAxis(Axis):
def get_data_interval(self):
'return the Interval instance for this axis data limits'
return self.axes.zz_dataLim.intervalx
@@ -0,0 +1,197 @@
# 3dproj.py
#
"""
Various transforms used for by the 3D code
"""
import numpy as np
import numpy.linalg as linalg
def line2d(p0, p1):
"""
Return 2D equation of line in the form ax+by+c = 0
"""
# x + x1 = 0
x0, y0 = p0[:2]
x1, y1 = p1[:2]
#
if x0 == x1:
a = -1
b = 0
c = x1
elif y0 == y1:
a = 0
b = 1
c = -y1
else:
a = (y0-y1)
b = (x0-x1)
c = (x0*y1 - x1*y0)
return a, b, c
def line2d_dist(l, p):
"""
Distance from line to point
line is a tuple of coefficients a,b,c
"""
a, b, c = l
x0, y0 = p
return abs((a*x0 + b*y0 + c)/np.sqrt(a**2+b**2))
def line2d_seg_dist(p1, p2, p0):
"""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.sqrt((x01 - u*x21)**2 + (y01 - u*y21)**2)
return d
def mod(v):
"""3d vector length"""
return np.sqrt(v[0]**2+v[1]**2+v[2]**2)
def world_transformation(xmin, xmax,
ymin, ymax,
zmin, zmax):
dx, dy, dz = (xmax-xmin), (ymax-ymin), (zmax-zmin)
return np.array([
[1.0/dx,0,0,-xmin/dx],
[0,1.0/dy,0,-ymin/dy],
[0,0,1.0/dz,-zmin/dz],
[0,0,0,1.0]])
def view_transformation(E, R, V):
n = (E - R)
## new
# n /= mod(n)
# u = np.cross(V,n)
# u /= mod(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 / mod(n)
u = np.cross(V, n)
u = u / mod(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)
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)
transform = proj_transform
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 proj_trans_clip_points(points, M):
xs, ys, zs = zip(*points)
return proj_transform_clip(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)
@@ -0,0 +1,11 @@
import os
# Check that the test directories exist
if not os.path.exists(os.path.join(
os.path.dirname(__file__), 'baseline_images')):
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.')
File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

@@ -0,0 +1,580 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg height="345.6pt" version="1.1" viewBox="0 0 460.8 345.6" width="460.8pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 345.6
L 460.8 345.6
L 460.8 0
L 0 0
z
" style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d="M 57.6 307.584
L 414.72 307.584
L 414.72 41.472
L 57.6 41.472
z
" style="fill:#ffffff;"/>
</g>
<g id="pane3d_1">
<g id="patch_3">
<path d="M 103.645013 249.524693
L 204.185554 184.643006
L 204.185554 54.879632
L 103.645013 119.761319
" style="fill:#f2f2f2;opacity:0.5;stroke:#f2f2f2;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_2">
<g id="patch_4">
<path d="M 204.185554 184.643006
L 378.326878 222.102465
L 378.326878 92.339091
L 204.185554 54.879632
" style="fill:#e6e6e6;opacity:0.5;stroke:#e6e6e6;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_3">
<g id="patch_5">
<path d="M 103.645013 249.524693
L 277.786338 286.984152
L 378.326878 222.102465
L 204.185554 184.643006
" style="fill:#ececec;opacity:0.5;stroke:#ececec;stroke-linejoin:miter;"/>
</g>
</g>
<g id="axis3d_1">
<g id="line2d_1">
<path d="M 103.645013 249.524693
L 277.786338 286.984152
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="Line3DCollection_1">
<path d="M 107.12784 250.273882
L 207.66838 185.392195
L 207.66838 55.628821
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 140.562974 257.466098
L 241.103515 192.584411
L 241.103515 62.821037
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 173.998109 264.658314
L 274.538649 199.776627
L 274.538649 70.013253
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 207.433243 271.850531
L 307.973783 206.968844
L 307.973783 77.205469
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 240.868377 279.042747
L 341.408918 214.16106
L 341.408918 84.397686
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 274.303511 286.234963
L 374.844052 221.353276
L 374.844052 91.589902
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
</g>
<g id="xtick_1">
<g id="line2d_2">
<path d="M 107.932164 249.754828
L 105.519191 251.311989
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_1">
<!-- 0.0 -->
<defs>
<path d="M 31.78125 66.40625
Q 24.171875 66.40625 20.328125 58.90625
Q 16.5 51.421875 16.5 36.375
Q 16.5 21.390625 20.328125 13.890625
Q 24.171875 6.390625 31.78125 6.390625
Q 39.453125 6.390625 43.28125 13.890625
Q 47.125 21.390625 47.125 36.375
Q 47.125 51.421875 43.28125 58.90625
Q 39.453125 66.40625 31.78125 66.40625
M 31.78125 74.21875
Q 44.046875 74.21875 50.515625 64.515625
Q 56.984375 54.828125 56.984375 36.375
Q 56.984375 17.96875 50.515625 8.265625
Q 44.046875 -1.421875 31.78125 -1.421875
Q 19.53125 -1.421875 13.0625 8.265625
Q 6.59375 17.96875 6.59375 36.375
Q 6.59375 54.828125 13.0625 64.515625
Q 19.53125 74.21875 31.78125 74.21875
" id="DejaVuSans-30"/>
<path d="M 10.6875 12.40625
L 21 12.40625
L 21 0
L 10.6875 0
z
" id="DejaVuSans-2e"/>
</defs>
<g transform="translate(92.052333 271.664175)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-30"/>
</g>
</g>
</g>
<g id="xtick_2">
<g id="line2d_3">
<path d="M 141.367299 256.947045
L 138.954326 258.504205
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_2">
<!-- 0.2 -->
<defs>
<path d="M 19.1875 8.296875
L 53.609375 8.296875
L 53.609375 0
L 7.328125 0
L 7.328125 8.296875
Q 12.9375 14.109375 22.625 23.890625
Q 32.328125 33.6875 34.8125 36.53125
Q 39.546875 41.84375 41.421875 45.53125
Q 43.3125 49.21875 43.3125 52.78125
Q 43.3125 58.59375 39.234375 62.25
Q 35.15625 65.921875 28.609375 65.921875
Q 23.96875 65.921875 18.8125 64.3125
Q 13.671875 62.703125 7.8125 59.421875
L 7.8125 69.390625
Q 13.765625 71.78125 18.9375 73
Q 24.125 74.21875 28.421875 74.21875
Q 39.75 74.21875 46.484375 68.546875
Q 53.21875 62.890625 53.21875 53.421875
Q 53.21875 48.921875 51.53125 44.890625
Q 49.859375 40.875 45.40625 35.40625
Q 44.1875 33.984375 37.640625 27.21875
Q 31.109375 20.453125 19.1875 8.296875
" id="DejaVuSans-32"/>
</defs>
<g transform="translate(125.487467 278.856391)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-32"/>
</g>
</g>
</g>
<g id="xtick_3">
<g id="line2d_4">
<path d="M 174.802433 264.139261
L 172.38946 265.696421
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_3">
<!-- 0.4 -->
<defs>
<path d="M 37.796875 64.3125
L 12.890625 25.390625
L 37.796875 25.390625
z
M 35.203125 72.90625
L 47.609375 72.90625
L 47.609375 25.390625
L 58.015625 25.390625
L 58.015625 17.1875
L 47.609375 17.1875
L 47.609375 0
L 37.796875 0
L 37.796875 17.1875
L 4.890625 17.1875
L 4.890625 26.703125
z
" id="DejaVuSans-34"/>
</defs>
<g transform="translate(158.922601 286.048608)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-34"/>
</g>
</g>
</g>
<g id="xtick_4">
<g id="line2d_5">
<path d="M 208.237567 271.331477
L 205.824594 272.888638
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_4">
<!-- 0.6 -->
<defs>
<path d="M 33.015625 40.375
Q 26.375 40.375 22.484375 35.828125
Q 18.609375 31.296875 18.609375 23.390625
Q 18.609375 15.53125 22.484375 10.953125
Q 26.375 6.390625 33.015625 6.390625
Q 39.65625 6.390625 43.53125 10.953125
Q 47.40625 15.53125 47.40625 23.390625
Q 47.40625 31.296875 43.53125 35.828125
Q 39.65625 40.375 33.015625 40.375
M 52.59375 71.296875
L 52.59375 62.3125
Q 48.875 64.0625 45.09375 64.984375
Q 41.3125 65.921875 37.59375 65.921875
Q 27.828125 65.921875 22.671875 59.328125
Q 17.53125 52.734375 16.796875 39.40625
Q 19.671875 43.65625 24.015625 45.921875
Q 28.375 48.1875 33.59375 48.1875
Q 44.578125 48.1875 50.953125 41.515625
Q 57.328125 34.859375 57.328125 23.390625
Q 57.328125 12.15625 50.6875 5.359375
Q 44.046875 -1.421875 33.015625 -1.421875
Q 20.359375 -1.421875 13.671875 8.265625
Q 6.984375 17.96875 6.984375 36.375
Q 6.984375 53.65625 15.1875 63.9375
Q 23.390625 74.21875 37.203125 74.21875
Q 40.921875 74.21875 44.703125 73.484375
Q 48.484375 72.75 52.59375 71.296875
" id="DejaVuSans-36"/>
</defs>
<g transform="translate(192.357736 293.240824)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-36"/>
</g>
</g>
</g>
<g id="xtick_5">
<g id="line2d_6">
<path d="M 241.672701 278.523693
L 239.259728 280.080854
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_5">
<!-- 0.8 -->
<defs>
<path d="M 31.78125 34.625
Q 24.75 34.625 20.71875 30.859375
Q 16.703125 27.09375 16.703125 20.515625
Q 16.703125 13.921875 20.71875 10.15625
Q 24.75 6.390625 31.78125 6.390625
Q 38.8125 6.390625 42.859375 10.171875
Q 46.921875 13.96875 46.921875 20.515625
Q 46.921875 27.09375 42.890625 30.859375
Q 38.875 34.625 31.78125 34.625
M 21.921875 38.8125
Q 15.578125 40.375 12.03125 44.71875
Q 8.5 49.078125 8.5 55.328125
Q 8.5 64.0625 14.71875 69.140625
Q 20.953125 74.21875 31.78125 74.21875
Q 42.671875 74.21875 48.875 69.140625
Q 55.078125 64.0625 55.078125 55.328125
Q 55.078125 49.078125 51.53125 44.71875
Q 48 40.375 41.703125 38.8125
Q 48.828125 37.15625 52.796875 32.3125
Q 56.78125 27.484375 56.78125 20.515625
Q 56.78125 9.90625 50.3125 4.234375
Q 43.84375 -1.421875 31.78125 -1.421875
Q 19.734375 -1.421875 13.25 4.234375
Q 6.78125 9.90625 6.78125 20.515625
Q 6.78125 27.484375 10.78125 32.3125
Q 14.796875 37.15625 21.921875 38.8125
M 18.3125 54.390625
Q 18.3125 48.734375 21.84375 45.5625
Q 25.390625 42.390625 31.78125 42.390625
Q 38.140625 42.390625 41.71875 45.5625
Q 45.3125 48.734375 45.3125 54.390625
Q 45.3125 60.0625 41.71875 63.234375
Q 38.140625 66.40625 31.78125 66.40625
Q 25.390625 66.40625 21.84375 63.234375
Q 18.3125 60.0625 18.3125 54.390625
" id="DejaVuSans-38"/>
</defs>
<g transform="translate(225.79287 300.43304)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-38"/>
</g>
</g>
</g>
<g id="xtick_6">
<g id="line2d_7">
<path d="M 275.107836 285.715909
L 272.694863 287.27307
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_6">
<!-- 1.0 -->
<defs>
<path d="M 12.40625 8.296875
L 28.515625 8.296875
L 28.515625 63.921875
L 10.984375 60.40625
L 10.984375 69.390625
L 28.421875 72.90625
L 38.28125 72.90625
L 38.28125 8.296875
L 54.390625 8.296875
L 54.390625 0
L 12.40625 0
z
" id="DejaVuSans-31"/>
</defs>
<g transform="translate(259.228004 307.625256)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-31"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-30"/>
</g>
</g>
</g>
</g>
<g id="axis3d_2">
<g id="line2d_8">
<path d="M 378.326878 222.102465
L 277.786338 286.984152
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="Line3DCollection_2">
<path d="M 105.655824 118.463685
L 105.655824 248.227059
L 279.797149 285.686518
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 124.959608 106.006401
L 124.959608 235.769775
L 299.100932 273.229234
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 144.263392 93.549117
L 144.263392 223.312491
L 318.404716 260.771951
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 163.567176 81.091833
L 163.567176 210.855207
L 337.7085 248.314667
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 182.870959 68.634549
L 182.870959 198.397923
L 357.012284 235.857383
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 202.174743 56.177265
L 202.174743 185.940639
L 376.316068 223.400099
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
</g>
<g id="xtick_7">
<g id="line2d_9">
<path d="M 278.404018 285.386843
L 282.58341 286.28587
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_7">
<!-- 0.0 -->
<g transform="translate(284.18462 305.13377)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-30"/>
</g>
</g>
</g>
<g id="xtick_8">
<g id="line2d_10">
<path d="M 297.707802 272.929559
L 301.887194 273.828586
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_8">
<!-- 0.2 -->
<g transform="translate(303.488404 292.676486)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-32"/>
</g>
</g>
</g>
<g id="xtick_9">
<g id="line2d_11">
<path d="M 317.011586 260.472275
L 321.190977 261.371302
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_9">
<!-- 0.4 -->
<g transform="translate(322.792188 280.219203)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-34"/>
</g>
</g>
</g>
<g id="xtick_10">
<g id="line2d_12">
<path d="M 336.315369 248.014991
L 340.494761 248.914018
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_10">
<!-- 0.6 -->
<g transform="translate(342.095972 267.761919)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-36"/>
</g>
</g>
</g>
<g id="xtick_11">
<g id="line2d_13">
<path d="M 355.619153 235.557707
L 359.798545 236.456734
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_11">
<!-- 0.8 -->
<g transform="translate(361.399755 255.304635)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-38"/>
</g>
</g>
</g>
<g id="xtick_12">
<g id="line2d_14">
<path d="M 374.922937 223.100423
L 379.102329 223.99945
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_12">
<!-- 1.0 -->
<g transform="translate(380.703539 242.847351)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-31"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-30"/>
</g>
</g>
</g>
</g>
<g id="axis3d_3">
<g id="line2d_15">
<path d="M 378.326878 222.102465
L 378.326878 92.339091
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="Line3DCollection_3">
<path d="M 378.326878 219.507198
L 204.185554 182.047738
L 103.645013 246.929425
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 378.326878 194.59263
L 204.185554 157.13317
L 103.645013 222.014857
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 378.326878 169.678062
L 204.185554 132.218603
L 103.645013 197.10029
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 378.326878 144.763494
L 204.185554 107.304035
L 103.645013 172.185722
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 378.326878 119.848926
L 204.185554 82.389467
L 103.645013 147.271154
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
<path d="M 378.326878 94.934359
L 204.185554 57.474899
L 103.645013 122.356586
" style="fill:none;stroke:#b0b0b0;stroke-width:0.8;"/>
</g>
<g id="xtick_13">
<g id="line2d_16">
<path d="M 376.933748 219.207522
L 381.11314 220.106549
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_13">
<!-- 0.0 -->
<g transform="translate(389.838295 225.162594)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-30"/>
</g>
</g>
</g>
<g id="xtick_14">
<g id="line2d_17">
<path d="M 376.933748 194.292954
L 381.11314 195.191981
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_14">
<!-- 0.2 -->
<g transform="translate(389.838295 200.248026)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-32"/>
</g>
</g>
</g>
<g id="xtick_15">
<g id="line2d_18">
<path d="M 376.933748 169.378386
L 381.11314 170.277413
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_15">
<!-- 0.4 -->
<g transform="translate(389.838295 175.333458)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-34"/>
</g>
</g>
</g>
<g id="xtick_16">
<g id="line2d_19">
<path d="M 376.933748 144.463819
L 381.11314 145.362846
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_16">
<!-- 0.6 -->
<g transform="translate(389.838295 150.41889)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-36"/>
</g>
</g>
</g>
<g id="xtick_17">
<g id="line2d_20">
<path d="M 376.933748 119.549251
L 381.11314 120.448278
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_17">
<!-- 0.8 -->
<g transform="translate(389.838295 125.504323)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-30"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-38"/>
</g>
</g>
</g>
<g id="xtick_18">
<g id="line2d_21">
<path d="M 376.933748 94.634683
L 381.11314 95.53371
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.8;"/>
</g>
<g id="text_18">
<!-- 1.0 -->
<g transform="translate(389.838295 100.589755)scale(0.1 -0.1)">
<use xlink:href="#DejaVuSans-31"/>
<use x="63.623047" xlink:href="#DejaVuSans-2e"/>
<use x="95.410156" xlink:href="#DejaVuSans-30"/>
</g>
</g>
</g>
</g>
<g id="axes_1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

@@ -0,0 +1,997 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg height="432pt" version="1.1" viewBox="0 0 576 432" width="576pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 432
L 576 432
L 576 0
L 0 0
z
" style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d="M 72 388.8
L 518.4 388.8
L 518.4 43.2
L 72 43.2
z
" style="fill:#ffffff;"/>
</g>
<g id="pane3d_1">
<g id="patch_3">
<path d="M 131.177218 312.43458
L 258.939234 227.701291
L 256.812132 65.789882
L 121.926546 141.921
" style="fill:#f2f2f2;opacity:0.5;stroke:#f2f2f2;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_2">
<g id="patch_4">
<path d="M 258.939234 227.701291
L 465.901687 275.072667
L 474.560748 108.278886
L 256.812132 65.789882
" style="fill:#e6e6e6;opacity:0.5;stroke:#e6e6e6;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_3">
<g id="patch_5">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
L 465.901687 275.072667
L 258.939234 227.701291
" style="fill:#ececec;opacity:0.5;stroke:#ececec;stroke-linejoin:miter;"/>
</g>
</g>
<g id="axis3d_1">
<g id="line2d_1">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_1">
<path d="M 135.341407 313.489655
L 262.91012 228.610182
L 260.98105 66.603357
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 185.898981 326.299368
L 311.080141 239.635758
L 311.581097 76.476879
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 237.565599 339.390079
L 360.229327 250.885453
L 363.262586 86.56142
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 290.378159 352.771135
L 410.387838 262.366172
L 416.060561 96.86382
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 344.375213 366.452305
L 461.587089 274.085104
L 470.011596 107.391216
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_1">
<g id="line2d_2">
<path d="M 136.440324 312.758477
L 133.139482 314.954734
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_2">
<g id="line2d_3">
<path d="M 186.978165 325.552244
L 183.736553 327.79643
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_3">
<g id="line2d_4">
<path d="M 238.623915 338.62648
L 235.444941 340.920181
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_4">
<g id="line2d_5">
<path d="M 291.414412 351.990512
L 288.301669 354.335385
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_5">
<g id="line2d_6">
<path d="M 345.388141 365.654082
L 342.345417 368.051858
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_2">
<g id="line2d_7">
<path d="M 465.901687 275.072667
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_2">
<path d="M 124.83963 140.276819
L 133.925947 310.61159
L 351.454544 365.608914
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 147.806962 127.313768
L 155.613677 296.228029
L 371.363131 349.859733
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 170.190782 114.680059
L 176.778102 282.191531
L 390.769477 334.507862
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 192.013047 102.363299
L 197.437937 268.489682
L 409.692352 319.538453
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 213.294627 90.351708
L 217.611014 255.110658
L 428.1496 304.937391
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 234.055372 78.634083
L 237.314336 242.04318
L 446.158198 290.691243
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 254.31417 67.199763
L 256.564121 229.276494
L 463.734308 276.787226
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_6">
<g id="line2d_8">
<path d="M 349.633738 365.148563
L 355.10024 366.530648
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_7">
<g id="line2d_9">
<path d="M 369.558238 349.411067
L 374.976914 350.758059
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_8">
<g id="line2d_10">
<path d="M 388.980278 334.070441
L 394.351787 335.38366
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_9">
<g id="line2d_11">
<path d="M 407.91863 319.11186
L 413.243626 320.392561
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_10">
<g id="line2d_12">
<path d="M 426.39114 304.521228
L 431.67027 305.770604
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_11">
<g id="line2d_13">
<path d="M 444.414786 290.285133
L 449.648694 291.504319
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_12">
<g id="line2d_14">
<path d="M 462.005733 276.390809
L 467.195055 277.580886
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_3">
<g id="line2d_15">
<path d="M 465.901687 275.072667
L 474.560748 108.278886
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_3">
<path d="M 466.066381 271.900274
L 258.89869 224.615172
L 131.001569 309.196909
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 468.068718 233.330576
L 258.406032 187.114858
L 128.865116 269.816616
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 470.120301 193.812297
L 257.901772 148.731448
L 126.674356 229.435289
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 472.222968 153.310006
L 257.385496 109.433377
L 124.42719 188.014268
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 474.378652 111.786488
L 256.856768 69.187556
L 122.121412 145.512876
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_13">
<g id="line2d_16">
<path d="M 464.337863 271.505747
L 469.527014 272.690148
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_14">
<g id="line2d_17">
<path d="M 466.318508 232.944779
L 471.572824 234.102983
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_15">
<g id="line2d_18">
<path d="M 468.347848 193.435781
L 473.668985 194.566132
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_16">
<g id="line2d_19">
<path d="M 470.4277 152.943356
L 475.817377 154.044099
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_17">
<g id="line2d_20">
<path d="M 472.559975 111.430323
L 478.01998 112.499596
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axes_1">
<g id="patch_6">
<path clip-path="url(#p21179d0b58)" d="M 260.499651 227.097258
L 268.156793 228.848353
L 268.156793 228.848353
L 260.499651 227.097258
L 260.499651 227.097258
L 260.499651 227.097258
" style="fill:#00bfbf;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_7">
<path clip-path="url(#p21179d0b58)" d="M 270.074909 229.287003
L 277.76275 231.045119
L 277.708309 223.58529
L 270.002751 221.835072
L 270.074909 229.287003
L 270.074909 229.287003
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_8">
<path clip-path="url(#p21179d0b58)" d="M 279.688564 231.485529
L 287.407289 233.250708
L 287.342871 218.276467
L 279.588343 216.527207
L 279.688564 231.485529
L 279.688564 231.485529
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_9">
<path clip-path="url(#p21179d0b58)" d="M 289.340847 233.69289
L 297.090643 235.465174
L 297.061569 212.921283
L 289.257507 211.173065
L 289.340847 233.69289
L 289.340847 233.69289
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_10">
<path clip-path="url(#p21179d0b58)" d="M 299.031992 235.909137
L 306.813047 237.68857
L 306.865508 207.51913
L 299.01134 205.77204
L 299.031992 235.909137
L 299.031992 235.909137
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_11">
<path clip-path="url(#p21179d0b58)" d="M 308.762234 238.134326
L 316.574737 239.92095
L 316.755816 202.069386
L 308.85096 200.323511
L 308.762234 238.134326
L 308.762234 238.134326
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_12">
<path clip-path="url(#p21179d0b58)" d="M 221.567951 252.927395
L 229.342356 254.7622
L 229.342356 254.7622
L 221.567951 252.927395
L 221.567951 252.927395
L 221.567951 252.927395
" style="fill:#00bfbf;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_13">
<path clip-path="url(#p21179d0b58)" d="M 318.531811 240.368509
L 326.375953 242.162369
L 326.733638 196.57142
L 318.777503 194.82685
L 318.531811 240.368509
L 318.531811 240.368509
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_14">
<path clip-path="url(#p21179d0b58)" d="M 231.289938 255.221841
L 239.096252 257.064177
L 238.948695 249.488986
L 231.124117 247.654725
L 231.289938 255.221841
L 231.289938 255.221841
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_15">
<path clip-path="url(#p21179d0b58)" d="M 328.34096 242.611743
L 336.216934 244.412883
L 336.800141 191.024589
L 328.792127 189.281416
L 328.34096 242.611743
L 328.34096 242.611743
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_16">
<path clip-path="url(#p21179d0b58)" d="M 241.051836 257.525707
L 248.890254 259.37562
L 248.640551 244.168829
L 240.765212 242.335195
L 241.051836 257.525707
L 241.051836 257.525707
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_17">
<path clip-path="url(#p21179d0b58)" d="M 338.189924 244.864081
L 346.097924 246.672545
L 346.956513 185.428239
L 338.896008 183.686557
L 338.189924 244.864081
L 338.189924 244.864081
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_18">
<path clip-path="url(#p21179d0b58)" d="M 250.853889 259.839049
L 258.72461 261.696586
L 258.419069 238.801101
L 250.492374 236.968177
L 250.853889 259.839049
L 250.853889 259.839049
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_19">
<path clip-path="url(#p21179d0b58)" d="M 348.078945 247.125581
L 356.019167 248.941413
L 357.203963 179.781703
L 349.090345 178.041609
L 348.078945 247.125581
L 348.078945 247.125581
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_20">
<path clip-path="url(#p21179d0b58)" d="M 260.696346 262.161927
L 268.599571 264.027135
L 268.285419 233.385159
L 260.30676 231.553033
L 260.696346 262.161927
L 260.696346 262.161927
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_21">
<path clip-path="url(#p21179d0b58)" d="M 358.008267 249.396296
L 365.980908 251.219543
L 367.543722 174.084303
L 359.376359 172.345897
L 358.008267 249.396296
L 358.008267 249.396296
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_22">
<path clip-path="url(#p21179d0b58)" d="M 270.579458 264.494399
L 278.515388 266.367326
L 278.240787 227.920352
L 270.209548 226.089112
L 270.579458 264.494399
L 270.579458 264.494399
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_23">
<path clip-path="url(#p21179d0b58)" d="M 180.753946 280.00639
L 188.648137 281.931054
L 188.648137 281.931054
L 180.753946 280.00639
L 180.753946 280.00639
L 180.753946 280.00639
" style="fill:#00bfbf;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_24">
<path clip-path="url(#p21179d0b58)" d="M 367.978138 251.676285
L 375.983398 253.506991
L 377.977041 168.33535
L 369.755292 166.598732
L 367.978138 251.676285
L 367.978138 251.676285
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_25">
<path clip-path="url(#p21179d0b58)" d="M 280.503477 266.836526
L 288.472315 268.717219
L 288.286385 222.406015
L 280.201938 220.575753
L 280.503477 266.836526
L 280.503477 266.836526
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_26">
<path clip-path="url(#p21179d0b58)" d="M 190.625825 282.41323
L 198.553201 284.345986
L 198.303435 276.652891
L 190.357228 274.728385
L 190.625825 282.41323
L 190.625825 282.41323
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_27">
<path clip-path="url(#p21179d0b58)" d="M 377.988807 253.965604
L 386.026885 255.803815
L 388.505197 162.534139
L 380.228409 160.799415
L 377.988807 253.965604
L 377.988807 253.965604
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_28">
<path clip-path="url(#p21179d0b58)" d="M 290.468657 269.188367
L 298.470609 271.076875
L 298.423445 216.841472
L 290.285152 215.012282
L 290.468657 269.188367
L 290.468657 269.188367
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_29">
<path clip-path="url(#p21179d0b58)" d="M 200.539212 284.83019
L 208.499984 286.771088
L 208.046785 271.326594
L 200.047939 269.402329
L 200.539212 284.83019
L 200.539212 284.83019
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_30">
<path clip-path="url(#p21179d0b58)" d="M 388.040524 256.26431
L 396.111623 258.110072
L 399.12949 156.679956
L 390.796997 154.947232
L 388.040524 256.26431
L 388.040524 256.26431
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_31">
<path clip-path="url(#p21179d0b58)" d="M 300.475254 271.549984
L 308.510528 273.446355
L 308.653222 211.226033
L 300.460434 209.398011
L 300.475254 271.549984
L 300.475254 271.549984
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_32">
<path clip-path="url(#p21179d0b58)" d="M 210.49437 287.257335
L 218.488749 289.206427
L 217.879397 265.9515
L 209.82728 264.027562
L 210.49437 287.257335
L 210.49437 287.257335
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_33">
<path clip-path="url(#p21179d0b58)" d="M 398.133543 258.572461
L 406.237867 260.425821
L 409.851241 150.772072
L 401.462368 149.041457
L 398.133543 258.572461
L 398.133543 258.572461
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_34">
<path clip-path="url(#p21179d0b58)" d="M 310.523529 273.921436
L 318.592331 275.82572
L 318.976993 205.558999
L 310.729051 203.732242
L 310.523529 273.921436
L 310.523529 273.921436
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_35">
<path clip-path="url(#p21179d0b58)" d="M 220.491563 289.694728
L 228.519763 291.652065
L 227.802504 260.526937
L 219.696471 258.603413
L 220.491563 289.694728
L 220.491563 289.694728
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_36">
<path clip-path="url(#p21179d0b58)" d="M 408.268119 260.890115
L 416.405873 262.751121
L 420.671798 144.809743
L 412.225858 143.081351
L 408.268119 260.890115
L 408.268119 260.890115
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_37">
<path clip-path="url(#p21179d0b58)" d="M 320.613741 276.302785
L 328.716283 278.215033
L 329.39606 199.839653
L 321.092292 198.014264
L 320.613741 276.302785
L 320.613741 276.302785
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_38">
<path clip-path="url(#p21179d0b58)" d="M 230.531059 292.142435
L 238.593294 294.10807
L 237.817361 255.052218
L 229.656756 253.129197
L 230.531059 292.142435
L 230.531059 292.142435
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_39">
<path clip-path="url(#p21179d0b58)" d="M 418.444508 263.217332
L 426.6159 265.08603
L 431.592533 138.792215
L 423.088826 137.066159
L 418.444508 263.217332
L 418.444508 263.217332
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_40">
<path clip-path="url(#p21179d0b58)" d="M 137.917747 308.427057
L 145.93419 310.448348
L 145.93419 310.448348
L 137.917747 308.427057
L 137.917747 308.427057
L 137.917747 308.427057
" style="fill:#00bfbf;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_41">
<path clip-path="url(#p21179d0b58)" d="M 330.746155 278.694094
L 338.882648 280.614355
L 339.911748 194.067269
L 331.551472 192.243351
L 330.746155 278.694094
L 330.746155 278.694094
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_42">
<path clip-path="url(#p21179d0b58)" d="M 240.613126 294.600521
L 248.709614 296.574507
L 247.925245 249.526644
L 239.709404 247.60422
L 240.613126 294.600521
L 240.613126 294.600521
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_43">
<path clip-path="url(#p21179d0b58)" d="M 428.662971 265.554171
L 436.868209 267.430609
L 442.614843 132.718716
L 434.052658 130.995116
L 428.662971 265.554171
L 428.662971 265.554171
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_44">
<path clip-path="url(#p21179d0b58)" d="M 147.94261 310.954757
L 155.993592 312.984756
L 155.631492 305.171263
L 147.561097 303.149684
L 147.94261 310.954757
L 147.94261 310.954757
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_45">
<path clip-path="url(#p21179d0b58)" d="M 340.921034 281.095426
L 349.091692 283.023749
L 350.525409 188.241106
L 342.107929 186.418765
L 340.921034 281.095426
L 340.921034 281.095426
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_46">
<path clip-path="url(#p21179d0b58)" d="M 250.738037 297.069052
L 258.868995 299.051443
L 258.12746 243.949504
L 249.855704 242.02777
L 250.738037 297.069052
L 250.738037 297.069052
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_47">
<path clip-path="url(#p21179d0b58)" d="M 438.923768 267.900691
L 447.163064 269.784918
L 453.740153 126.588463
L 445.118766 124.86744
L 438.923768 267.900691
L 438.923768 267.900691
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_48">
<path clip-path="url(#p21179d0b58)" d="M 158.010674 313.493349
L 166.096418 315.532113
L 165.419444 299.844847
L 157.294437 297.823064
L 158.010674 313.493349
L 158.010674 313.493349
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_49">
<path clip-path="url(#p21179d0b58)" d="M 351.138646 283.506842
L 359.343686 285.44328
L 361.238416 182.360407
L 352.763027 180.539753
L 351.138646 283.506842
L 351.138646 283.506842
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_50">
<path clip-path="url(#p21179d0b58)" d="M 260.906063 299.548096
L 269.071714 301.538945
L 268.425331 238.320072
L 260.096971 236.399126
L 260.906063 299.548096
L 260.906063 299.548096
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_51">
<path clip-path="url(#p21179d0b58)" d="M 449.227163 270.256952
L 457.500729 272.149016
L 464.969913 120.400656
L 456.288587 118.682334
L 449.227163 270.256952
L 449.227163 270.256952
" style="fill:#ff0000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_52">
<path clip-path="url(#p21179d0b58)" d="M 168.122219 316.042905
L 176.24295 318.090491
L 175.299323 294.468407
L 167.119032 292.446503
L 168.122219 316.042905
L 168.122219 316.042905
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_53">
<path clip-path="url(#p21179d0b58)" d="M 361.399262 285.928408
L 369.6389 287.873011
L 372.052173 176.424404
L 363.518154 174.605549
L 361.399262 285.928408
L 361.399262 285.928408
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_54">
<path clip-path="url(#p21179d0b58)" d="M 271.117483 302.037719
L 279.318047 304.03708
L 278.820211 232.637609
L 270.434545 230.717551
L 271.117483 302.037719
L 271.117483 302.037719
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_55">
<path clip-path="url(#p21179d0b58)" d="M 178.277526 318.603495
L 186.433472 320.65996
L 185.272431 289.041233
L 177.036172 287.019297
L 178.277526 318.603495
L 178.277526 318.603495
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_56">
<path clip-path="url(#p21179d0b58)" d="M 371.703154 288.360187
L 379.977609 290.313008
L 382.968106 170.432314
L 374.374726 168.615373
L 371.703154 288.360187
L 371.703154 288.360187
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_57">
<path clip-path="url(#p21179d0b58)" d="M 281.372573 304.537989
L 289.608276 306.545918
L 289.313477 226.901363
L 280.869791 224.982296
L 281.372573 304.537989
L 281.372573 304.537989
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_58">
<path clip-path="url(#p21179d0b58)" d="M 188.476881 321.175191
L 196.668272 323.240594
L 195.340093 283.562605
L 187.047171 281.540727
L 188.476881 321.175191
L 188.476881 321.175191
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_59">
<path clip-path="url(#p21179d0b58)" d="M 382.050595 290.802244
L 390.36009 292.763334
L 393.987672 164.383336
L 385.334186 162.568428
L 382.050595 290.802244
L 382.050595 290.802244
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_60">
<path clip-path="url(#p21179d0b58)" d="M 291.671614 307.048975
L 299.942683 309.065526
L 299.906532 221.110566
L 291.4041 219.192595
L 291.671614 307.048975
L 291.671614 307.048975
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_61">
<path clip-path="url(#p21179d0b58)" d="M 198.720571 323.758066
L 206.947638 325.832464
L 205.503661 278.031787
L 197.153367 276.010059
L 198.720571 323.758066
L 198.720571 323.758066
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_62">
<path clip-path="url(#p21179d0b58)" d="M 392.441863 293.254645
L 400.786619 295.224057
L 405.112352 158.276658
L 396.398001 156.463904
L 392.441863 293.254645
L 392.441863 293.254645
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_63">
<path clip-path="url(#p21179d0b58)" d="M 302.014891 309.570746
L 310.321553 311.595975
L 310.600806 215.264437
L 302.038888 213.34767
L 302.014891 309.570746
L 302.014891 309.570746
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_64">
<path clip-path="url(#p21179d0b58)" d="M 209.008885 326.352193
L 217.271862 328.435645
L 215.764511 272.44803
L 207.356125 270.426547
L 209.008885 326.352193
L 209.008885 326.352193
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_65">
<path clip-path="url(#p21179d0b58)" d="M 402.877236 295.717454
L 411.257479 297.695241
L 416.343657 152.11145
L 407.567671 150.300974
L 402.877236 295.717454
L 402.877236 295.717454
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_66">
<path clip-path="url(#p21179d0b58)" d="M 312.402687 312.103372
L 320.745173 314.137334
L 321.397758 209.362177
L 312.775601 207.446727
L 312.402687 312.103372
L 312.402687 312.103372
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_67">
<path clip-path="url(#p21179d0b58)" d="M 219.342116 328.957645
L 227.641238 331.050212
L 226.124048 266.810569
L 217.656835 264.78943
L 219.342116 328.957645
L 219.342116 328.957645
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_68">
<path clip-path="url(#p21179d0b58)" d="M 413.356996 298.190739
L 421.772953 300.176955
L 427.683129 145.886866
L 418.844721 144.078796
L 413.356996 298.190739
L 413.356996 298.190739
" style="fill:#008000;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_69">
<path clip-path="url(#p21179d0b58)" d="M 322.835293 314.646921
L 331.213834 316.689675
L 332.298873 203.402977
L 323.615711 201.488957
L 322.835293 314.646921
L 322.835293 314.646921
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_70">
<path clip-path="url(#p21179d0b58)" d="M 229.720558 331.574498
L 238.056064 333.676237
L 236.5837 261.118627
L 228.056915 259.097933
L 229.720558 331.574498
L 229.720558 331.574498
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_71">
<path clip-path="url(#p21179d0b58)" d="M 333.312997 317.201467
L 341.727828 319.253068
L 343.305665 197.386006
L 334.560718 195.473534
L 333.312997 317.201467
L 333.312997 317.201467
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_72">
<path clip-path="url(#p21179d0b58)" d="M 240.144509 334.202824
L 248.516638 336.313798
L 247.144928 255.37141
L 238.55781 253.351264
L 240.144509 334.202824
L 240.144509 334.202824
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_73">
<path clip-path="url(#p21179d0b58)" d="M 343.836093 319.767079
L 352.287451 321.827586
L 354.41968 191.310421
L 345.612153 189.399619
L 343.836093 319.767079
L 343.836093 319.767079
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_74">
<path clip-path="url(#p21179d0b58)" d="M 250.614269 336.842701
L 259.023263 338.962971
L 257.809217 249.568109
L 249.160992 247.548618
L 250.614269 336.842701
L 250.614269 336.842701
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_75">
<path clip-path="url(#p21179d0b58)" d="M 354.404877 322.34383
L 362.893 324.4133
L 365.64249 185.175362
L 356.771575 183.266353
L 354.404877 322.34383
L 354.404877 322.34383
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_76">
<path clip-path="url(#p21179d0b58)" d="M 261.13014 339.494205
L 269.576243 341.623831
L 268.578084 243.707899
L 259.867964 241.689173
L 261.13014 339.494205
L 261.13014 339.494205
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_77">
<path clip-path="url(#p21179d0b58)" d="M 365.019647 324.931793
L 373.544775 327.010285
L 376.975703 178.979951
L 368.040576 177.072862
L 365.019647 324.931793
L 365.019647 324.931793
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_78">
<path clip-path="url(#p21179d0b58)" d="M 271.692429 342.157413
L 280.175886 344.296458
L 279.453075 237.789938
L 270.680256 235.77209
L 271.692429 342.157413
L 271.692429 342.157413
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_79">
<path clip-path="url(#p21179d0b58)" d="M 375.680703 327.531041
L 384.243079 329.618615
L 388.420955 172.723292
L 379.420776 170.818256
L 375.680703 327.531041
L 375.680703 327.531041
" style="fill:#0000ff;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_80">
<path clip-path="url(#p21179d0b58)" d="M 282.301441 344.832402
L 290.822502 346.980928
L 290.435766 231.813369
L 281.599432 229.796515
L 282.301441 344.832402
L 282.301441 344.832402
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_81">
<path clip-path="url(#p21179d0b58)" d="M 292.95749 347.51925
L 301.516405 349.677321
L 301.527764 225.777317
L 292.627082 223.761576
L 292.95749 347.51925
L 292.95749 347.51925
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_82">
<path clip-path="url(#p21179d0b58)" d="M 303.660887 350.218037
L 312.257909 352.385717
L 312.730711 219.680888
L 303.764833 217.666385
L 303.660887 350.218037
L 303.660887 350.218037
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_83">
<path clip-path="url(#p21179d0b58)" d="M 314.41195 352.928843
L 323.047333 355.106195
L 324.046278 213.523174
L 315.01434 211.510034
L 314.41195 352.928843
L 314.41195 352.928843
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_84">
<path clip-path="url(#p21179d0b58)" d="M 325.210997 355.651748
L 333.884999 357.838837
L 335.476173 207.303245
L 326.377295 205.291599
L 325.210997 355.651748
L 325.210997 355.651748
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
<g id="patch_85">
<path clip-path="url(#p21179d0b58)" d="M 336.05835 358.386832
L 344.771232 360.583725
L 347.022138 201.020153
L 337.855423 199.010134
L 336.05835 358.386832
L 336.05835 358.386832
" style="fill:#bfbf00;opacity:0.8;stroke:#000000;stroke-linejoin:miter;"/>
</g>
</g>
</g>
<defs>
<clipPath id="p21179d0b58">
<rect height="345.6" width="446.4" x="72.0" y="43.2"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

@@ -0,0 +1,424 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg height="432pt" version="1.1" viewBox="0 0 576 432" width="576pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 432
L 576 432
L 576 0
L 0 0
z
" style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d="M 72 388.8
L 518.4 388.8
L 518.4 43.2
L 72 43.2
z
" style="fill:#ffffff;"/>
</g>
<g id="pane3d_1">
<g id="patch_3">
<path d="M 131.177218 312.43458
L 258.939234 227.701291
L 256.812132 65.789882
L 121.926546 141.921
" style="fill:#f2f2f2;opacity:0.5;stroke:#f2f2f2;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_2">
<g id="patch_4">
<path d="M 258.939234 227.701291
L 465.901687 275.072667
L 474.560748 108.278886
L 256.812132 65.789882
" style="fill:#e6e6e6;opacity:0.5;stroke:#e6e6e6;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_3">
<g id="patch_5">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
L 465.901687 275.072667
L 258.939234 227.701291
" style="fill:#ececec;opacity:0.5;stroke:#ececec;stroke-linejoin:miter;"/>
</g>
</g>
<g id="axis3d_1">
<g id="line2d_1">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_1">
<path d="M 135.341407 313.489655
L 262.91012 228.610182
L 260.98105 66.603357
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 160.483807 319.859956
L 286.874573 234.095375
L 286.148026 71.514157
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 185.898981 326.299368
L 311.080141 239.635758
L 311.581097 76.476879
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 211.591391 332.809024
L 335.530482 245.232165
L 337.284506 81.492351
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 237.565599 339.390079
L 360.229327 250.885453
L 363.262586 86.56142
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 263.826267 346.043713
L 385.180483 256.596492
L 389.519764 91.684949
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 290.378159 352.771135
L 410.387838 262.366172
L 416.060561 96.86382
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 317.226148 359.573579
L 435.855357 268.1954
L 442.889598 102.098935
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 344.375213 366.452305
L 461.587089 274.085104
L 470.011596 107.391216
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_1">
<g id="line2d_2">
<path d="M 136.440324 312.758477
L 133.139482 314.954734
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_2">
<g id="line2d_3">
<path d="M 161.572996 319.120869
L 158.301354 321.340896
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_3">
<g id="line2d_4">
<path d="M 186.978165 325.552244
L 183.736553 327.79643
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_4">
<g id="line2d_5">
<path d="M 212.660287 332.05373
L 209.449556 334.322471
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_5">
<g id="line2d_6">
<path d="M 238.623915 338.62648
L 235.444941 340.920181
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_6">
<g id="line2d_7">
<path d="M 264.873705 345.271673
L 261.727385 347.590748
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_7">
<g id="line2d_8">
<path d="M 291.414412 351.990512
L 288.301669 354.335385
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_8">
<g id="line2d_9">
<path d="M 318.2509 358.784229
L 315.17268 361.155332
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_9">
<g id="line2d_10">
<path d="M 345.388141 365.654082
L 342.345417 368.051858
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_2">
<g id="line2d_11">
<path d="M 465.901687 275.072667
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_2">
<path d="M 124.83963 140.276819
L 133.925947 310.61159
L 351.454544 365.608914
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 142.120716 130.523157
L 150.241556 299.790884
L 366.433824 353.759184
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 159.070412 120.956535
L 166.260112 289.167187
L 381.127889 342.135079
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 175.698161 111.571626
L 181.989655 278.735167
L 395.544809 330.730218
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 192.013047 102.363299
L 197.437937 268.489682
L 409.692352 319.538453
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 208.023819 93.326618
L 212.612435 258.425775
L 423.577998 308.55387
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 223.738902 84.456828
L 227.520363 248.53866
L 437.208951 297.770766
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 239.166411 75.749348
L 242.168684 238.823719
L 450.592157 287.18365
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 254.31417 67.199763
L 256.564121 229.276494
L 463.734308 276.787226
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_10">
<g id="line2d_12">
<path d="M 349.633738 365.148563
L 355.10024 366.530648
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_11">
<g id="line2d_13">
<path d="M 364.624972 353.307638
L 370.055544 354.663277
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_12">
<g id="line2d_14">
<path d="M 379.33087 341.692089
L 384.725881 343.022034
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_13">
<g id="line2d_15">
<path d="M 393.7595 330.295542
L 399.119319 331.600517
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_14">
<g id="line2d_16">
<path d="M 407.91863 319.11186
L 413.243626 320.392561
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_15">
<g id="line2d_17">
<path d="M 421.815742 308.135135
L 427.106278 309.392234
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_16">
<g id="line2d_18">
<path d="M 435.458042 297.359676
L 440.714481 298.593818
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_17">
<g id="line2d_19">
<path d="M 448.852474 286.779996
L 454.075176 287.991806
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_18">
<g id="line2d_20">
<path d="M 462.005733 276.390809
L 467.195055 277.580886
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_3">
<g id="line2d_21">
<path d="M 465.901687 275.072667
L 474.560748 108.278886
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_3">
<path d="M 466.066381 271.900274
L 258.89869 224.615172
L 131.001569 309.196909
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 468.068718 233.330576
L 258.406032 187.114858
L 128.865116 269.816616
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 470.120301 193.812297
L 257.901772 148.731448
L 126.674356 229.435289
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 472.222968 153.310006
L 257.385496 109.433377
L 124.42719 188.014268
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 474.378652 111.786488
L 256.856768 69.187556
L 122.121412 145.512876
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_19">
<g id="line2d_22">
<path d="M 464.337863 271.505747
L 469.527014 272.690148
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_20">
<g id="line2d_23">
<path d="M 466.318508 232.944779
L 471.572824 234.102983
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_21">
<g id="line2d_24">
<path d="M 468.347848 193.435781
L 473.668985 194.566132
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_22">
<g id="line2d_25">
<path d="M 470.4277 152.943356
L 475.817377 154.044099
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_23">
<g id="line2d_26">
<path d="M 472.559975 111.430323
L 478.01998 112.499596
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axes_1">
<g id="Poly3DCollection_1">
<path clip-path="url(#pcca6d018d3)" d="M 146.614877 231.721406
L 159.531667 234.760121
L 172.520089 237.815687
L 185.580739 240.888245
L 206.9384 238.637304
L 211.921148 247.084907
L 225.202136 250.209301
L 238.55781 253.351264
L 251.988802 256.510946
L 273.510159 254.196155
L 279.079303 262.88407
L 292.740111 266.097818
L 306.478837 269.329895
L 320.29615 272.580461
L 341.981907 270.199062
L 349.694458 264.604042
L 357.331505 259.063798
L 364.89415 253.577527
L 358.716192 245.031573
L 379.800541 242.763785
L 387.146389 237.434788
L 394.42204 232.156715
L 401.628495 226.92884
L 395.32285 218.782963
L 415.83773 216.620845
L 422.842419 211.539339
L 429.781733 206.50526
L 436.656583 201.517946
L 430.241548 193.744696
L 417.085468 190.927682
L 403.999065 188.125588
L 390.981787 185.338295
L 371.012017 187.377288
L 365.152429 179.807647
L 352.339273 177.064061
L 339.593091 174.334816
L 326.91336 171.619799
L 307.081871 173.605938
L 301.751182 166.232009
L 289.267714 163.559017
L 276.848656 160.899816
L 264.49351 158.254301
L 244.799849 160.189615
L 237.331514 164.797835
L 229.795882 169.44758
L 222.19204 174.139414
L 226.991762 181.627026
L 206.775985 183.651651
L 198.961861 188.473236
L 191.075701 193.339269
L 183.116505 198.250368
L 187.74066 206.090024
L 166.974908 208.21029
L 158.79041 213.260408
L 150.528683 218.358178
L 142.188629 223.50428
L 146.614877 231.721406
z
M 226.125278 214.832541
L 246.901313 212.686108
L 252.064316 220.740441
L 231.143354 222.919043
z
M 291.508349 229.724251
L 312.438258 227.518392
L 318.168271 235.79634
L 297.091411 238.035719
z
M 264.806923 189.974015
L 285.031614 187.924978
L 290.353193 195.612876
L 269.991265 197.69191
z
M 329.187688 204.184864
L 349.557251 202.080425
L 355.427242 209.976754
L 334.918599 212.112419
z
" style="fill:#dddcdc;"/>
</g>
</g>
</g>
<defs>
<clipPath id="pcca6d018d3">
<rect height="345.6" width="446.4" x="72.0" y="43.2"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

@@ -0,0 +1,479 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg height="432pt" version="1.1" viewBox="0 0 576 432" width="576pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 432
L 576 432
L 576 0
L 0 0
z
" style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d="M 72 388.8
L 518.4 388.8
L 518.4 43.2
L 72 43.2
z
" style="fill:#ffffff;"/>
</g>
<g id="pane3d_1">
<g id="patch_3">
<path d="M 131.177218 312.43458
L 258.939234 227.701291
L 256.812132 65.789882
L 121.926546 141.921
" style="fill:#f2f2f2;opacity:0.5;stroke:#f2f2f2;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_2">
<g id="patch_4">
<path d="M 258.939234 227.701291
L 465.901687 275.072667
L 474.560748 108.278886
L 256.812132 65.789882
" style="fill:#e6e6e6;opacity:0.5;stroke:#e6e6e6;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_3">
<g id="patch_5">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
L 465.901687 275.072667
L 258.939234 227.701291
" style="fill:#ececec;opacity:0.5;stroke:#ececec;stroke-linejoin:miter;"/>
</g>
</g>
<g id="axis3d_1">
<g id="line2d_1">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_1">
<path d="M 135.341407 313.489655
L 262.91012 228.610182
L 260.98105 66.603357
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 168.925005 321.998692
L 294.916126 235.935996
L 294.595943 73.162588
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 202.996171 330.631263
L 327.352943 243.360419
L 328.6864 79.814614
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 237.565599 339.390079
L 360.229327 250.885453
L 363.262586 86.56142
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 272.644301 348.277928
L 393.554274 258.513158
L 398.334956 93.405046
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 308.243613 357.297684
L 427.337028 266.245651
L 433.914269 100.347591
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 344.375213 366.452305
L 461.587089 274.085104
L 470.011596 107.391216
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_1">
<g id="line2d_2">
<path d="M 136.440324 312.758477
L 133.139482 314.954734
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_2">
<g id="line2d_3">
<path d="M 170.01089 321.25694
L 166.749164 323.484977
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_3">
<g id="line2d_4">
<path d="M 204.068528 329.878707
L 200.847407 332.139218
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_4">
<g id="line2d_5">
<path d="M 238.623915 338.62648
L 235.444941 340.920181
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_5">
<g id="line2d_6">
<path d="M 273.688045 347.503043
L 270.552814 349.830669
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_6">
<g id="line2d_7">
<path d="M 309.272234 356.51126
L 306.1824 358.87357
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_7">
<g id="line2d_8">
<path d="M 345.388141 365.654082
L 342.345417 368.051858
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_2">
<g id="line2d_9">
<path d="M 465.901687 275.072667
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_2">
<path d="M 124.83963 140.276819
L 133.925947 310.61159
L 351.454544 365.608914
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 140.217137 131.597561
L 148.44353 300.983356
L 364.783694 355.064559
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 155.331963 123.066563
L 162.725688 291.511259
L 377.886771 344.699046
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 170.190782 114.680059
L 176.778102 282.191531
L 390.769477 334.507862
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 184.800044 106.434408
L 190.606273 273.020523
L 403.437326 324.486646
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 199.165982 98.326092
L 204.215524 263.994706
L 415.895649 314.631182
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 213.294627 90.351708
L 217.611014 255.110658
L 428.1496 304.937391
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 227.191809 82.507964
L 230.79774 246.365064
L 440.204168 295.401326
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 240.86317 74.791676
L 243.780544 237.754714
L 452.06418 286.01917
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 254.31417 67.199763
L 256.564121 229.276494
L 463.734308 276.787226
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_8">
<g id="line2d_10">
<path d="M 349.633738 365.148563
L 355.10024 366.530648
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_9">
<g id="line2d_11">
<path d="M 362.973521 354.612048
L 368.408066 355.970588
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_10">
<g id="line2d_12">
<path d="M 376.087133 344.254176
L 381.490015 345.589767
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_11">
<g id="line2d_13">
<path d="M 388.980278 334.070441
L 394.351787 335.38366
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_12">
<g id="line2d_14">
<path d="M 401.658469 324.056489
L 406.998897 325.347894
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_13">
<g id="line2d_15">
<path d="M 414.127038 314.208109
L 419.436673 315.478239
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_14">
<g id="line2d_16">
<path d="M 426.39114 304.521228
L 431.67027 305.770604
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_15">
<g id="line2d_17">
<path d="M 438.455763 294.991906
L 443.704674 296.221032
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_16">
<g id="line2d_18">
<path d="M 450.325737 285.61633
L 455.544713 286.825695
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_17">
<g id="line2d_19">
<path d="M 462.005733 276.390809
L 467.195055 277.580886
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_3">
<g id="line2d_20">
<path d="M 465.901687 275.072667
L 474.560748 108.278886
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_3">
<path d="M 466.066381 271.900274
L 258.89869 224.615172
L 131.001569 309.196909
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 467.061506 252.73185
L 258.653786 205.973486
L 129.940004 289.629551
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 468.068718 233.330576
L 258.406032 187.114858
L 128.865116 269.816616
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 469.088241 213.692183
L 258.155377 168.035478
L 127.776653 249.753452
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 470.120301 193.812297
L 257.901772 148.731448
L 126.674356 229.435289
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 471.16513 173.686436
L 257.645162 129.198777
L 125.557959 208.857234
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 472.222968 153.310006
L 257.385496 109.433377
L 124.42719 188.014268
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 473.294058 132.6783
L 257.122716 89.431065
L 123.28177 166.901243
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 474.378652 111.786488
L 256.856768 69.187556
L 122.121412 145.512876
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_18">
<g id="line2d_21">
<path d="M 464.337863 271.505747
L 469.527014 272.690148
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_19">
<g id="line2d_22">
<path d="M 465.322209 252.341622
L 470.543739 253.513124
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_20">
<g id="line2d_23">
<path d="M 466.318508 232.944779
L 471.572824 234.102983
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_21">
<g id="line2d_24">
<path d="M 467.326979 213.310956
L 472.614495 214.455446
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_22">
<g id="line2d_25">
<path d="M 468.347848 193.435781
L 473.668985 194.566132
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_23">
<g id="line2d_26">
<path d="M 469.381343 173.314777
L 474.736531 174.43055
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_24">
<g id="line2d_27">
<path d="M 470.4277 152.943356
L 475.817377 154.044099
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_25">
<g id="line2d_28">
<path d="M 471.487162 132.316812
L 476.911775 133.40206
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_26">
<g id="line2d_29">
<path d="M 472.559975 111.430323
L 478.01998 112.499596
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axes_1">
<g id="line2d_30">
<path clip-path="url(#p47a4741ada)" d="M 358.008267 249.396296
L 374.473482 255.287414
L 385.204896 263.156503
L 389.782754 272.354677
L 388.149984 282.164018
L 380.635403 291.835913
L 367.952315 300.639085
L 351.162935 307.913929
L 331.604056 313.126742
L 310.777359 315.915538
L 290.216467 316.119289
L 271.349408 313.785027
L 255.377108 309.151518
L 243.185022 302.612954
L 235.297325 294.669669
L 231.874059 285.87435
L 232.744053 276.781437
L 237.462184 267.905144
L 245.378817 259.688714
L 255.711222 252.485092
L 267.609997 246.547546
L 280.21683 242.02806
L 292.712505 238.98131
L 304.355657 237.372442
L 314.513396 237.087461
L 322.684826 237.945574
L 328.517978 239.713191
L 331.820005 242.119466
L 332.559995 244.873134
L 330.86354 247.680215
L 326.998445 250.261762
L 321.351661 252.370542
L 314.398514 253.805334
L 306.666423 254.421586
L 298.696206 254.137441
L 291.004529 252.934683
L 284.050953 250.854738
L 278.212304 247.990498
L 273.765992 244.475162
L 270.882655 240.469504
L 269.627317 236.148951
L 269.967465 231.691628
L 271.785972 227.268149
L 274.896793 223.033595
L 279.061595 219.121721
L 284.005919 215.641221
L 289.434011 212.673688
L 295.04189 210.272862
L 300.528651 208.464745
L 305.606267 207.248273
L 310.00833 206.596294
L 313.498211 206.456746
L 315.877054 206.75407
L 316.991867 207.390967
L 316.743741 208.250741
L 315.095894 209.200492
L 312.080947 210.09544
L 307.806483 210.784607
L 302.457728 211.117916
L 296.296078 210.954602
L 289.652351 210.172554
L 282.913958 208.677941
L 276.50589 206.414262
L 270.866153 203.36982
L 266.417182 199.582649
L 263.535474 195.142094
L 262.522141 190.186602
L 263.577078 184.897701
L 266.779084 179.490563
L 272.07352 174.201914
L 279.268237 169.276248
L 288.037662 164.951286
L 297.934345 161.443552
L 308.407016 158.934667
L 318.824249 157.558766
L 328.50315 157.391278
L 336.742897 158.439288
L 342.863229 160.633841
L 346.247962 163.824811
L 346.393075 167.779398
L 342.957725 172.185649
L 335.814731 176.662658
L 325.094903 180.778928
L 311.217535 184.079656
L 294.898362 186.122333
L 277.12716 186.51807
L 259.110658 184.974053
L 242.182317 181.330945
L 227.687687 175.588811
L 216.860247 167.916547
L 210.705713 158.642704
L 209.911289 148.229241
L 214.790749 137.232893
L 225.268303 126.260641
L 240.896674 115.925708
L 260.899743 106.808926
L 284.228494 99.427952
L 309.62045 94.214443
L 335.656335 91.497472
L 360.812012 91.490539
" style="fill:none;stroke:#0000ff;stroke-linecap:square;"/>
</g>
</g>
</g>
<defs>
<clipPath id="p47a4741ada">
<rect height="345.6" width="446.4" x="72" y="43.2"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@@ -0,0 +1,311 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg height="432pt" version="1.1" viewBox="0 0 576 432" width="576pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 432
L 576 432
L 576 0
L 0 0
z
" style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d="M 72 388.8
L 518.4 388.8
L 518.4 43.2
L 72 43.2
z
" style="fill:#ffffff;"/>
</g>
<g id="pane3d_1">
<g id="patch_3">
<path d="M 131.177218 312.43458
L 258.939234 227.701291
L 256.812132 65.789882
L 121.926546 141.921
" style="fill:#f2f2f2;opacity:0.5;stroke:#f2f2f2;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_2">
<g id="patch_4">
<path d="M 258.939234 227.701291
L 465.901687 275.072667
L 474.560748 108.278886
L 256.812132 65.789882
" style="fill:#e6e6e6;opacity:0.5;stroke:#e6e6e6;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_3">
<g id="patch_5">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
L 465.901687 275.072667
L 258.939234 227.701291
" style="fill:#ececec;opacity:0.5;stroke:#ececec;stroke-linejoin:miter;"/>
</g>
</g>
<g id="axis3d_1">
<g id="line2d_1">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_1">
<path d="M 135.341407 313.489655
L 262.91012 228.610182
L 260.98105 66.603357
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 175.699895 323.715238
L 301.368748 237.412929
L 301.375668 74.485507
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 216.763541 334.119486
L 340.45025 246.358245
L 342.458008 82.50185
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 258.55099 344.707123
L 380.169882 255.449622
L 384.245785 90.655845
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 301.081547 355.48304
L 420.543401 264.690666
L 426.757325 98.951066
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 344.375213 366.452305
L 461.587089 274.085104
L 470.011596 107.391216
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_1">
<g id="line2d_2">
<path d="M 136.440324 312.758477
L 133.139482 314.954734
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_2">
<g id="line2d_3">
<path d="M 176.783114 322.971344
L 173.529389 325.205819
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_3">
<g id="line2d_4">
<path d="M 217.830344 333.362542
L 214.625895 335.636242
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_4">
<g id="line2d_5">
<path d="M 259.600627 343.936782
L 256.447704 346.250748
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_5">
<g id="line2d_6">
<path d="M 302.113237 354.698943
L 299.014189 357.054256
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_6">
<g id="line2d_7">
<path d="M 345.388141 365.654082
L 342.345417 368.051858
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_2">
<g id="line2d_8">
<path d="M 465.901687 275.072667
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_2">
<path d="M 124.83963 140.276819
L 133.925947 310.61159
L 351.454544 365.608914
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 152.329688 124.761085
L 159.887815 293.39337
L 375.283965 346.75806
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 178.985882 109.715994
L 185.101569 276.671308
L 398.395625 328.47501
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 204.845585 95.120453
L 209.599088 260.424262
L 420.82147 310.734489
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 229.94397 80.95461
L 233.410462 244.632276
L 442.591583 293.512702
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 254.31417 67.199763
L 256.564121 229.276494
L 463.734308 276.787226
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_7">
<g id="line2d_9">
<path d="M 349.633738 365.148563
L 355.10024 366.530648
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_8">
<g id="line2d_10">
<path d="M 373.482228 346.311677
L 378.891419 347.651812
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_9">
<g id="line2d_11">
<path d="M 396.612642 328.041968
L 401.965468 329.342034
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_10">
<g id="line2d_12">
<path d="M 419.056931 310.3142
L 424.35433 311.575967
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_11">
<g id="line2d_13">
<path d="M 440.845178 293.104611
L 446.08808 294.329747
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_12">
<g id="line2d_14">
<path d="M 462.005733 276.390809
L 467.195055 277.580886
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_3">
<g id="line2d_15">
<path d="M 465.901687 275.072667
L 474.560748 108.278886
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_3">
<path d="M 466.066381 271.900274
L 258.89869 224.615172
L 131.001569 309.196909
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 467.664368 241.119299
L 258.505479 194.684584
L 129.296686 277.771554
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 469.293643 209.735667
L 258.104894 164.192771
L 127.557309 245.710375
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 470.955131 177.731505
L 257.696727 133.123797
L 125.782379 212.993876
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 472.6498 145.088225
L 257.28076 101.46112
L 123.970796 179.601758
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 474.378652 111.786488
L 256.856768 69.187556
L 122.121412 145.512876
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_13">
<g id="line2d_16">
<path d="M 464.337863 271.505747
L 469.527014 272.690148
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_14">
<g id="line2d_17">
<path d="M 465.91854 240.731713
L 471.159693 241.895285
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_15">
<g id="line2d_18">
<path d="M 467.530154 209.35537
L 472.82436 210.497067
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_16">
<g id="line2d_19">
<path d="M 469.173622 177.358863
L 474.521965 178.477587
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_17">
<g id="line2d_20">
<path d="M 470.849899 144.72362
L 476.253496 145.818222
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_18">
<g id="line2d_21">
<path d="M 472.559975 111.430323
L 478.01998 112.499596
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axes_1">
<g id="Poly3DCollection_1">
<defs>
<path d="M 258.530268 -360.58077
L 467.563587 -319.561013
L 459.573423 -159.376984
" id="m1394a5d166" style="stroke:#000000;stroke-width:3;"/>
</defs>
<g clip-path="url(#p826a730be7)">
<use style="fill:#ff8080;fill-opacity:0.5;stroke:#000000;stroke-width:3;" x="0" xlink:href="#m1394a5d166" y="432"/>
</g>
</g>
<g id="Poly3DCollection_2">
<defs>
<path d="M 129.404241 -287.184409
L 258.530268 -360.58077
L 137.917747 -123.572943
z
" id="m4d49e4906d" style="stroke:#000000;stroke-width:3;"/>
</defs>
<g clip-path="url(#p826a730be7)">
<use style="fill:#8080ff;fill-opacity:0.5;stroke:#000000;stroke-width:3;" x="0" xlink:href="#m4d49e4906d" y="432"/>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="p826a730be7">
<rect height="345.6" width="446.4" x="72" y="43.2"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@@ -0,0 +1,284 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg height="432pt" version="1.1" viewBox="0 0 576 432" width="576pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 432
L 576 432
L 576 0
L 0 0
z
" style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d="M 72 388.8
L 518.4 388.8
L 518.4 43.2
L 72 43.2
z
" style="fill:#ffffff;"/>
</g>
<g id="pane3d_1">
<g id="patch_3">
<path d="M 131.177218 312.43458
L 258.939234 227.701291
L 256.812132 65.789882
L 121.926546 141.921
" style="fill:#f2f2f2;opacity:0.5;stroke:#f2f2f2;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_2">
<g id="patch_4">
<path d="M 258.939234 227.701291
L 465.901687 275.072667
L 474.560748 108.278886
L 256.812132 65.789882
" style="fill:#e6e6e6;opacity:0.5;stroke:#e6e6e6;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_3">
<g id="patch_5">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
L 465.901687 275.072667
L 258.939234 227.701291
" style="fill:#ececec;opacity:0.5;stroke:#ececec;stroke-linejoin:miter;"/>
</g>
</g>
<g id="axis3d_1">
<g id="line2d_1">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_1">
<path d="M 135.341407 313.489655
L 262.91012 228.610182
L 260.98105 66.603357
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 175.699895 323.715238
L 301.368748 237.412929
L 301.375668 74.485507
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 216.763541 334.119486
L 340.45025 246.358245
L 342.458008 82.50185
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 258.55099 344.707123
L 380.169882 255.449622
L 384.245785 90.655845
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 301.081547 355.48304
L 420.543401 264.690666
L 426.757325 98.951066
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 344.375213 366.452305
L 461.587089 274.085104
L 470.011596 107.391216
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_1">
<g id="line2d_2">
<path d="M 136.440324 312.758477
L 133.139482 314.954734
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_2">
<g id="line2d_3">
<path d="M 176.783114 322.971344
L 173.529389 325.205819
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_3">
<g id="line2d_4">
<path d="M 217.830344 333.362542
L 214.625895 335.636242
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_4">
<g id="line2d_5">
<path d="M 259.600627 343.936782
L 256.447704 346.250748
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_5">
<g id="line2d_6">
<path d="M 302.113237 354.698943
L 299.014189 357.054256
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_6">
<g id="line2d_7">
<path d="M 345.388141 365.654082
L 342.345417 368.051858
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_2">
<g id="line2d_8">
<path d="M 465.901687 275.072667
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_2">
<path d="M 124.83963 140.276819
L 133.925947 310.61159
L 351.454544 365.608914
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 152.329688 124.761085
L 159.887815 293.39337
L 375.283965 346.75806
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 178.985882 109.715994
L 185.101569 276.671308
L 398.395625 328.47501
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 204.845585 95.120453
L 209.599088 260.424262
L 420.82147 310.734489
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 229.94397 80.95461
L 233.410462 244.632276
L 442.591583 293.512702
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 254.31417 67.199763
L 256.564121 229.276494
L 463.734308 276.787226
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_7">
<g id="line2d_9">
<path d="M 349.633738 365.148563
L 355.10024 366.530648
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_8">
<g id="line2d_10">
<path d="M 373.482228 346.311677
L 378.891419 347.651812
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_9">
<g id="line2d_11">
<path d="M 396.612642 328.041968
L 401.965468 329.342034
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_10">
<g id="line2d_12">
<path d="M 419.056931 310.3142
L 424.35433 311.575967
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_11">
<g id="line2d_13">
<path d="M 440.845178 293.104611
L 446.08808 294.329747
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_12">
<g id="line2d_14">
<path d="M 462.005733 276.390809
L 467.195055 277.580886
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_3">
<g id="line2d_15">
<path d="M 465.901687 275.072667
L 474.560748 108.278886
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_3">
<path d="M 466.066381 271.900274
L 258.89869 224.615172
L 131.001569 309.196909
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 467.664368 241.119299
L 258.505479 194.684584
L 129.296686 277.771554
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 469.293643 209.735667
L 258.104894 164.192771
L 127.557309 245.710375
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 470.955131 177.731505
L 257.696727 133.123797
L 125.782379 212.993876
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 472.6498 145.088225
L 257.28076 101.46112
L 123.970796 179.601758
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 474.378652 111.786488
L 256.856768 69.187556
L 122.121412 145.512876
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_13">
<g id="line2d_16">
<path d="M 464.337863 271.505747
L 469.527014 272.690148
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_14">
<g id="line2d_17">
<path d="M 465.91854 240.731713
L 471.159693 241.895285
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_15">
<g id="line2d_18">
<path d="M 467.530154 209.35537
L 472.82436 210.497067
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_16">
<g id="line2d_19">
<path d="M 469.173622 177.358863
L 474.521965 178.477587
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_17">
<g id="line2d_20">
<path d="M 470.849899 144.72362
L 476.253496 145.818222
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_18">
<g id="line2d_21">
<path d="M 472.559975 111.430323
L 478.01998 112.499596
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axes_1">
<g id="Line3DCollection_4"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

@@ -0,0 +1,385 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.org/) -->
<svg height="432pt" version="1.1" viewBox="0 0 576 432" width="576pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style type="text/css">
*{stroke-linecap:butt;stroke-linejoin:round;}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 432
L 576 432
L 576 0
L 0 0
z
" style="fill:#ffffff;"/>
</g>
<g id="patch_2">
<path d="M 72 388.8
L 518.4 388.8
L 518.4 43.2
L 72 43.2
z
" style="fill:#ffffff;"/>
</g>
<g id="pane3d_1">
<g id="patch_3">
<path d="M 131.177218 312.43458
L 258.939234 227.701291
L 256.812132 65.789882
L 121.926546 141.921
" style="fill:#f2f2f2;opacity:0.5;stroke:#f2f2f2;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_2">
<g id="patch_4">
<path d="M 258.939234 227.701291
L 465.901687 275.072667
L 474.560748 108.278886
L 256.812132 65.789882
" style="fill:#e6e6e6;opacity:0.5;stroke:#e6e6e6;stroke-linejoin:miter;"/>
</g>
</g>
<g id="pane3d_3">
<g id="patch_5">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
L 465.901687 275.072667
L 258.939234 227.701291
" style="fill:#ececec;opacity:0.5;stroke:#ececec;stroke-linejoin:miter;"/>
</g>
</g>
<g id="axis3d_1">
<g id="line2d_1">
<path d="M 131.177218 312.43458
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_1">
<path d="M 135.341407 313.489655
L 262.91012 228.610182
L 260.98105 66.603357
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 175.699895 323.715238
L 301.368748 237.412929
L 301.375668 74.485507
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 216.763541 334.119486
L 340.45025 246.358245
L 342.458008 82.50185
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 258.55099 344.707123
L 380.169882 255.449622
L 384.245785 90.655845
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 301.081547 355.48304
L 420.543401 264.690666
L 426.757325 98.951066
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 344.375213 366.452305
L 461.587089 274.085104
L 470.011596 107.391216
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_1">
<g id="line2d_2">
<path d="M 136.440324 312.758477
L 133.139482 314.954734
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_2">
<g id="line2d_3">
<path d="M 176.783114 322.971344
L 173.529389 325.205819
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_3">
<g id="line2d_4">
<path d="M 217.830344 333.362542
L 214.625895 335.636242
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_4">
<g id="line2d_5">
<path d="M 259.600627 343.936782
L 256.447704 346.250748
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_5">
<g id="line2d_6">
<path d="M 302.113237 354.698943
L 299.014189 357.054256
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_6">
<g id="line2d_7">
<path d="M 345.388141 365.654082
L 342.345417 368.051858
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_2">
<g id="line2d_8">
<path d="M 465.901687 275.072667
L 348.929684 367.606266
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_2">
<path d="M 124.83963 140.276819
L 133.925947 310.61159
L 351.454544 365.608914
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 152.329688 124.761085
L 159.887815 293.39337
L 375.283965 346.75806
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 178.985882 109.715994
L 185.101569 276.671308
L 398.395625 328.47501
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 204.845585 95.120453
L 209.599088 260.424262
L 420.82147 310.734489
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 229.94397 80.95461
L 233.410462 244.632276
L 442.591583 293.512702
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 254.31417 67.199763
L 256.564121 229.276494
L 463.734308 276.787226
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_7">
<g id="line2d_9">
<path d="M 349.633738 365.148563
L 355.10024 366.530648
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_8">
<g id="line2d_10">
<path d="M 373.482228 346.311677
L 378.891419 347.651812
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_9">
<g id="line2d_11">
<path d="M 396.612642 328.041968
L 401.965468 329.342034
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_10">
<g id="line2d_12">
<path d="M 419.056931 310.3142
L 424.35433 311.575967
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_11">
<g id="line2d_13">
<path d="M 440.845178 293.104611
L 446.08808 294.329747
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_12">
<g id="line2d_14">
<path d="M 462.005733 276.390809
L 467.195055 277.580886
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axis3d_3">
<g id="line2d_15">
<path d="M 465.901687 275.072667
L 474.560748 108.278886
" style="fill:none;stroke:#000000;stroke-linecap:square;stroke-width:0.75;"/>
</g>
<g id="Line3DCollection_3">
<path d="M 466.066381 271.900274
L 258.89869 224.615172
L 131.001569 309.196909
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 467.664368 241.119299
L 258.505479 194.684584
L 129.296686 277.771554
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 469.293643 209.735667
L 258.104894 164.192771
L 127.557309 245.710375
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 470.955131 177.731505
L 257.696727 133.123797
L 125.782379 212.993876
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 472.6498 145.088225
L 257.28076 101.46112
L 123.970796 179.601758
" style="fill:none;stroke:#e6e6e6;"/>
<path d="M 474.378652 111.786488
L 256.856768 69.187556
L 122.121412 145.512876
" style="fill:none;stroke:#e6e6e6;"/>
</g>
<g id="xtick_13">
<g id="line2d_16">
<path d="M 464.337863 271.505747
L 469.527014 272.690148
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_14">
<g id="line2d_17">
<path d="M 465.91854 240.731713
L 471.159693 241.895285
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_15">
<g id="line2d_18">
<path d="M 467.530154 209.35537
L 472.82436 210.497067
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_16">
<g id="line2d_19">
<path d="M 469.173622 177.358863
L 474.521965 178.477587
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_17">
<g id="line2d_20">
<path d="M 470.849899 144.72362
L 476.253496 145.818222
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
<g id="xtick_18">
<g id="line2d_21">
<path d="M 472.559975 111.430323
L 478.01998 112.499596
" style="fill:none;stroke:#000000;stroke-linecap:square;"/>
</g>
</g>
</g>
<g id="axes_1">
<g id="Path3DCollection_1">
<defs>
<path d="M 0 2.236068
C 0.593012 2.236068 1.161816 2.000462 1.581139 1.581139
C 2.000462 1.161816 2.236068 0.593012 2.236068 0
C 2.236068 -0.593012 2.000462 -1.161816 1.581139 -1.581139
C 1.161816 -2.000462 0.593012 -2.236068 0 -2.236068
C -0.593012 -2.236068 -1.161816 -2.000462 -1.581139 -1.581139
C -2.000462 -1.161816 -2.236068 -0.593012 -2.236068 0
C -2.236068 0.593012 -2.000462 1.161816 -1.581139 1.581139
C -1.161816 2.000462 -0.593012 2.236068 0 2.236068
z
" id="C0_0_83d296332c"/>
</defs>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.3;stroke:#000000;stroke-opacity:0.3;" x="202.886915404" xlink:href="#C0_0_83d296332c" y="269.800202283"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.377324436644;stroke:#000000;stroke-opacity:0.377324436644;" x="215.93755177" xlink:href="#C0_0_83d296332c" y="262.041059838"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.454761630131;stroke:#000000;stroke-opacity:0.454761630131;" x="229.007218971" xlink:href="#C0_0_83d296332c" y="254.270602776"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.532311827277;stroke:#000000;stroke-opacity:0.532311827277;" x="242.095958663" xlink:href="#C0_0_83d296332c" y="246.48880633"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.609975275623;stroke:#000000;stroke-opacity:0.609975275623;" x="255.203812625" xlink:href="#C0_0_83d296332c" y="238.69564566"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.68775222343;stroke:#000000;stroke-opacity:0.68775222343;" x="268.33082276" xlink:href="#C0_0_83d296332c" y="230.891095854"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.765642919688;stroke:#000000;stroke-opacity:0.765642919688;" x="281.47703109" xlink:href="#C0_0_83d296332c" y="223.075131929"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.843647614114;stroke:#000000;stroke-opacity:0.843647614114;" x="294.642479763" xlink:href="#C0_0_83d296332c" y="215.247728824"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;fill-opacity:0.921766557157;stroke:#000000;stroke-opacity:0.921766557157;" x="307.827211048" xlink:href="#C0_0_83d296332c" y="207.40886141"/>
</g>
<g clip-path="url(#p9fde8b4859)">
<use style="fill:#ff0000;stroke:#000000;" x="321.03126734" xlink:href="#C0_0_83d296332c" y="199.558504482"/>
</g>
</g>
<g id="Path3DCollection_2">
<path clip-path="url(#p9fde8b4859)" d="M 334.254691 189.460565
L 332.018623 193.932701
L 336.490759 193.932701
z
" style="fill:#0000ff;fill-opacity:0.3;stroke:#000000;stroke-opacity:0.3;"/>
<path clip-path="url(#p9fde8b4859)" d="M 347.497525 181.587153
L 345.261457 186.059289
L 349.733593 186.059289
z
" style="fill:#0000ff;fill-opacity:0.377321109432;stroke:#000000;stroke-opacity:0.377321109432;"/>
<path clip-path="url(#p9fde8b4859)" d="M 360.759812 173.702175
L 358.523744 178.174311
L 362.99588 178.174311
z
" style="fill:#0000ff;fill-opacity:0.454755798984;stroke:#000000;stroke-opacity:0.454755798984;"/>
<path clip-path="url(#p9fde8b4859)" d="M 374.041595 165.805607
L 371.805527 170.277743
L 376.277663 170.277743
z
" style="fill:#0000ff;fill-opacity:0.532304319104;stroke:#000000;stroke-opacity:0.532304319104;"/>
<path clip-path="url(#p9fde8b4859)" d="M 387.342916 157.897422
L 385.106848 162.369558
L 389.578984 162.369558
z
" style="fill:#0000ff;fill-opacity:0.609966920976;stroke:#000000;stroke-opacity:0.609966920976;"/>
<path clip-path="url(#p9fde8b4859)" d="M 400.66382 149.977594
L 398.427752 154.44973
L 402.899888 154.44973
z
" style="fill:#0000ff;fill-opacity:0.687743856524;stroke:#000000;stroke-opacity:0.687743856524;"/>
<path clip-path="url(#p9fde8b4859)" d="M 414.004349 142.046099
L 411.768281 146.518235
L 416.240417 146.518235
z
" style="fill:#0000ff;fill-opacity:0.765635378415;stroke:#000000;stroke-opacity:0.765635378415;"/>
<path clip-path="url(#p9fde8b4859)" d="M 427.364546 134.102909
L 425.128478 138.575045
L 429.600614 138.575045
z
" style="fill:#0000ff;fill-opacity:0.84364174006;stroke:#000000;stroke-opacity:0.84364174006;"/>
<path clip-path="url(#p9fde8b4859)" d="M 440.744456 126.148
L 438.508388 130.620136
L 442.980524 130.620136
z
" style="fill:#0000ff;fill-opacity:0.921763195619;stroke:#000000;stroke-opacity:0.921763195619;"/>
<path clip-path="url(#p9fde8b4859)" d="M 454.144122 118.181346
L 451.908054 122.653482
L 456.38019 122.653482
z
" style="fill:#0000ff;stroke:#000000;"/>
</g>
</g>
</g>
<defs>
<clipPath id="p9fde8b4859">
<rect height="345.6" width="446.4" x="72.0" y="43.2"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 84 KiB

@@ -0,0 +1,3 @@
from matplotlib.testing.conftest import (mpl_test_settings,
mpl_image_comparison_parameters,
pytest_configure, pytest_unconfigure)
@@ -0,0 +1,43 @@
from matplotlib.testing.decorators import image_comparison
from mpl_toolkits.axes_grid1 import ImageGrid
import numpy as np
import matplotlib.pyplot as plt
@image_comparison(baseline_images=['imagegrid_cbar_mode'],
extensions=['png'],
remove_text=True,
style='mpl20')
def test_imagegrid_cbar_mode_edge():
X, Y = np.meshgrid(np.linspace(0, 6, 30), np.linspace(0, 6, 30))
arr = np.sin(X) * np.cos(Y) + 1j*(np.sin(3*Y) * np.cos(Y/2.))
fig = plt.figure(figsize=(18, 9))
positions = (241, 242, 243, 244, 245, 246, 247, 248)
directions = ['row']*4 + ['column']*4
cbar_locations = ['left', 'right', 'top', 'bottom']*2
for position, direction, location in zip(positions,
directions,
cbar_locations):
grid = ImageGrid(fig, position,
nrows_ncols=(2, 2),
direction=direction,
cbar_location=location,
cbar_size='20%',
cbar_mode='edge')
ax1, ax2, ax3, ax4, = grid
im1 = ax1.imshow(arr.real, cmap='nipy_spectral')
im2 = ax2.imshow(arr.imag, cmap='hot')
im3 = ax3.imshow(np.abs(arr), cmap='jet')
im4 = ax4.imshow(np.arctan2(arr.imag, arr.real), cmap='hsv')
# Some of these colorbars will be overridden by later ones,
# depending on the direction and cbar_location
ax1.cax.colorbar(im1)
ax2.cax.colorbar(im2)
ax3.cax.colorbar(im3)
ax4.cax.colorbar(im4)
@@ -0,0 +1,424 @@
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
from mpl_toolkits.axes_grid1 import host_subplot
from mpl_toolkits.axes_grid1 import make_axes_locatable
from mpl_toolkits.axes_grid1 import AxesGrid
from mpl_toolkits.axes_grid1 import ImageGrid
from mpl_toolkits.axes_grid1.inset_locator import (
zoomed_inset_axes,
mark_inset,
inset_axes,
BboxConnectorPatch
)
from mpl_toolkits.axes_grid1.anchored_artists import (
AnchoredSizeBar,
AnchoredDirectionArrows)
from matplotlib.colors import LogNorm
from matplotlib.transforms import Bbox, TransformedBbox, \
blended_transform_factory
from itertools import product
import pytest
import platform
import numpy as np
from numpy.testing import assert_array_equal, assert_array_almost_equal
@image_comparison(baseline_images=['divider_append_axes'])
def test_divider_append_axes():
# the random data
np.random.seed(0)
x = np.random.randn(1000)
y = np.random.randn(1000)
fig, axScatter = plt.subplots()
# the scatter plot:
axScatter.scatter(x, y)
# create new axes on the right and on the top of the current axes
# The first argument of the new_vertical(new_horizontal) method is
# the height (width) of the axes to be created in inches.
divider = make_axes_locatable(axScatter)
axHistbot = divider.append_axes("bottom", 1.2, pad=0.1, sharex=axScatter)
axHistright = divider.append_axes("right", 1.2, pad=0.1, sharey=axScatter)
axHistleft = divider.append_axes("left", 1.2, pad=0.1, sharey=axScatter)
axHisttop = divider.append_axes("top", 1.2, pad=0.1, sharex=axScatter)
# now determine nice limits by hand:
binwidth = 0.25
xymax = max(np.max(np.abs(x)), np.max(np.abs(y)))
lim = (int(xymax/binwidth) + 1) * binwidth
bins = np.arange(-lim, lim + binwidth, binwidth)
axHisttop.hist(x, bins=bins)
axHistbot.hist(x, bins=bins)
axHistleft.hist(y, bins=bins, orientation='horizontal')
axHistright.hist(y, bins=bins, orientation='horizontal')
axHistbot.invert_yaxis()
axHistleft.invert_xaxis()
axHisttop.xaxis.set_ticklabels(())
axHistbot.xaxis.set_ticklabels(())
axHistleft.yaxis.set_ticklabels(())
axHistright.yaxis.set_ticklabels(())
@image_comparison(baseline_images=['twin_axes_empty_and_removed'],
extensions=["png"], tol=1)
def test_twin_axes_empty_and_removed():
# Purely cosmetic font changes (avoid overlap)
matplotlib.rcParams.update({"font.size": 8})
matplotlib.rcParams.update({"xtick.labelsize": 8})
matplotlib.rcParams.update({"ytick.labelsize": 8})
generators = [ "twinx", "twiny", "twin" ]
modifiers = [ "", "host invisible", "twin removed", "twin invisible",
"twin removed\nhost invisible" ]
# Unmodified host subplot at the beginning for reference
h = host_subplot(len(modifiers)+1, len(generators), 2)
h.text(0.5, 0.5, "host_subplot", horizontalalignment="center",
verticalalignment="center")
# Host subplots with various modifications (twin*, visibility) applied
for i, (mod, gen) in enumerate(product(modifiers, generators),
len(generators)+1):
h = host_subplot(len(modifiers)+1, len(generators), i)
t = getattr(h, gen)()
if "twin invisible" in mod:
t.axis[:].set_visible(False)
if "twin removed" in mod:
t.remove()
if "host invisible" in mod:
h.axis[:].set_visible(False)
h.text(0.5, 0.5, gen + ("\n" + mod if mod else ""),
horizontalalignment="center", verticalalignment="center")
plt.subplots_adjust(wspace=0.5, hspace=1)
def test_axesgrid_colorbar_log_smoketest():
fig = plt.figure()
grid = AxesGrid(fig, 111, # modified to be only subplot
nrows_ncols=(1, 1),
label_mode="L",
cbar_location="top",
cbar_mode="single",
)
Z = 10000 * np.random.rand(10, 10)
im = grid[0].imshow(Z, interpolation="nearest", norm=LogNorm())
grid.cbar_axes[0].colorbar(im)
@image_comparison(
baseline_images=['inset_locator'], style='default', extensions=['png'],
remove_text=True)
def test_inset_locator():
def get_demo_image():
from matplotlib.cbook import get_sample_data
import numpy as np
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
z = np.load(f)
# z is a numpy array of 15x15
return z, (-3, 4, -4, 3)
fig, ax = plt.subplots(figsize=[5, 4])
# prepare the demo image
Z, extent = get_demo_image()
Z2 = np.zeros([150, 150], dtype="d")
ny, nx = Z.shape
Z2[30:30 + ny, 30:30 + nx] = Z
# extent = [-3, 4, -4, 3]
ax.imshow(Z2, extent=extent, interpolation="nearest",
origin="lower")
axins = zoomed_inset_axes(ax, zoom=6, loc='upper right')
axins.imshow(Z2, extent=extent, interpolation="nearest",
origin="lower")
axins.yaxis.get_major_locator().set_params(nbins=7)
axins.xaxis.get_major_locator().set_params(nbins=7)
# sub region of the original image
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
plt.xticks(visible=False)
plt.yticks(visible=False)
# draw a bbox of the region of the inset axes in the parent axes and
# connecting lines between the bbox and the inset axes area
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
asb = AnchoredSizeBar(ax.transData,
0.5,
'0.5',
loc='lower center',
pad=0.1, borderpad=0.5, sep=5,
frameon=False)
ax.add_artist(asb)
@image_comparison(
baseline_images=['inset_axes'], style='default', extensions=['png'],
remove_text=True)
def test_inset_axes():
def get_demo_image():
from matplotlib.cbook import get_sample_data
import numpy as np
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
z = np.load(f)
# z is a numpy array of 15x15
return z, (-3, 4, -4, 3)
fig, ax = plt.subplots(figsize=[5, 4])
# prepare the demo image
Z, extent = get_demo_image()
Z2 = np.zeros([150, 150], dtype="d")
ny, nx = Z.shape
Z2[30:30 + ny, 30:30 + nx] = Z
# extent = [-3, 4, -4, 3]
ax.imshow(Z2, extent=extent, interpolation="nearest",
origin="lower")
# creating our inset axes with a bbox_transform parameter
axins = inset_axes(ax, width=1., height=1., bbox_to_anchor=(1, 1),
bbox_transform=ax.transAxes)
axins.imshow(Z2, extent=extent, interpolation="nearest",
origin="lower")
axins.yaxis.get_major_locator().set_params(nbins=7)
axins.xaxis.get_major_locator().set_params(nbins=7)
# sub region of the original image
x1, x2, y1, y2 = -1.5, -0.9, -2.5, -1.9
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
plt.xticks(visible=False)
plt.yticks(visible=False)
# draw a bbox of the region of the inset axes in the parent axes and
# connecting lines between the bbox and the inset axes area
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")
asb = AnchoredSizeBar(ax.transData,
0.5,
'0.5',
loc='lower center',
pad=0.1, borderpad=0.5, sep=5,
frameon=False)
ax.add_artist(asb)
def test_inset_axes_complete():
dpi = 100
figsize = (6, 5)
fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
fig.subplots_adjust(.1, .1, .9, .9)
ins = inset_axes(ax, width=2., height=2., borderpad=0)
fig.canvas.draw()
assert_array_almost_equal(
ins.get_position().extents,
np.array(((0.9*figsize[0]-2.)/figsize[0],
(0.9*figsize[1]-2.)/figsize[1], 0.9, 0.9)))
ins = inset_axes(ax, width="40%", height="30%", borderpad=0)
fig.canvas.draw()
assert_array_almost_equal(
ins.get_position().extents,
np.array((.9-.8*.4, .9-.8*.3, 0.9, 0.9)))
ins = inset_axes(ax, width=1., height=1.2, bbox_to_anchor=(200, 100),
loc=3, borderpad=0)
fig.canvas.draw()
assert_array_almost_equal(
ins.get_position().extents,
np.array((200./dpi/figsize[0], 100./dpi/figsize[1],
(200./dpi+1)/figsize[0], (100./dpi+1.2)/figsize[1])))
ins1 = inset_axes(ax, width="35%", height="60%", loc=3, borderpad=1)
ins2 = inset_axes(ax, width="100%", height="100%",
bbox_to_anchor=(0, 0, .35, .60),
bbox_transform=ax.transAxes, loc=3, borderpad=1)
fig.canvas.draw()
assert_array_equal(ins1.get_position().extents,
ins2.get_position().extents)
with pytest.raises(ValueError):
ins = inset_axes(ax, width="40%", height="30%",
bbox_to_anchor=(0.4, 0.5))
with pytest.warns(UserWarning):
ins = inset_axes(ax, width="40%", height="30%",
bbox_transform=ax.transAxes)
@image_comparison(
baseline_images=['fill_facecolor'], extensions=['png'],
remove_text=True, style='mpl20')
def test_fill_facecolor():
fig, ax = plt.subplots(1, 5)
fig.set_size_inches(5, 5)
for i in range(1, 4):
ax[i].yaxis.set_visible(False)
ax[4].yaxis.tick_right()
bbox = Bbox.from_extents(0, 0.4, 1, 0.6)
# fill with blue by setting 'fc' field
bbox1 = TransformedBbox(bbox, ax[0].transData)
bbox2 = TransformedBbox(bbox, ax[1].transData)
# set color to BboxConnectorPatch
p = BboxConnectorPatch(
bbox1, bbox2, loc1a=1, loc2a=2, loc1b=4, loc2b=3,
ec="r", fc="b")
p.set_clip_on(False)
ax[0].add_patch(p)
# set color to marked area
axins = zoomed_inset_axes(ax[0], 1, loc='upper right')
axins.set_xlim(0, 0.2)
axins.set_ylim(0, 0.2)
plt.gca().axes.get_xaxis().set_ticks([])
plt.gca().axes.get_yaxis().set_ticks([])
mark_inset(ax[0], axins, loc1=2, loc2=4, fc="b", ec="0.5")
# fill with yellow by setting 'facecolor' field
bbox3 = TransformedBbox(bbox, ax[1].transData)
bbox4 = TransformedBbox(bbox, ax[2].transData)
# set color to BboxConnectorPatch
p = BboxConnectorPatch(
bbox3, bbox4, loc1a=1, loc2a=2, loc1b=4, loc2b=3,
ec="r", facecolor="y")
p.set_clip_on(False)
ax[1].add_patch(p)
# set color to marked area
axins = zoomed_inset_axes(ax[1], 1, loc='upper right')
axins.set_xlim(0, 0.2)
axins.set_ylim(0, 0.2)
plt.gca().axes.get_xaxis().set_ticks([])
plt.gca().axes.get_yaxis().set_ticks([])
mark_inset(ax[1], axins, loc1=2, loc2=4, facecolor="y", ec="0.5")
# fill with green by setting 'color' field
bbox5 = TransformedBbox(bbox, ax[2].transData)
bbox6 = TransformedBbox(bbox, ax[3].transData)
# set color to BboxConnectorPatch
p = BboxConnectorPatch(
bbox5, bbox6, loc1a=1, loc2a=2, loc1b=4, loc2b=3,
ec="r", color="g")
p.set_clip_on(False)
ax[2].add_patch(p)
# set color to marked area
axins = zoomed_inset_axes(ax[2], 1, loc='upper right')
axins.set_xlim(0, 0.2)
axins.set_ylim(0, 0.2)
plt.gca().axes.get_xaxis().set_ticks([])
plt.gca().axes.get_yaxis().set_ticks([])
mark_inset(ax[2], axins, loc1=2, loc2=4, color="g", ec="0.5")
# fill with green but color won't show if set fill to False
bbox7 = TransformedBbox(bbox, ax[3].transData)
bbox8 = TransformedBbox(bbox, ax[4].transData)
# BboxConnectorPatch won't show green
p = BboxConnectorPatch(
bbox7, bbox8, loc1a=1, loc2a=2, loc1b=4, loc2b=3,
ec="r", fc="g", fill=False)
p.set_clip_on(False)
ax[3].add_patch(p)
# marked area won't show green
axins = zoomed_inset_axes(ax[3], 1, loc='upper right')
axins.set_xlim(0, 0.2)
axins.set_ylim(0, 0.2)
axins.get_xaxis().set_ticks([])
axins.get_yaxis().set_ticks([])
mark_inset(ax[3], axins, loc1=2, loc2=4, fc="g", ec="0.5", fill=False)
@image_comparison(baseline_images=['zoomed_axes',
'inverted_zoomed_axes'],
extensions=['png'])
def test_zooming_with_inverted_axes():
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 2, 3])
ax.axis([1, 3, 1, 3])
inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc='lower right')
inset_ax.axis([1.1, 1.4, 1.1, 1.4])
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 2, 3])
ax.axis([3, 1, 3, 1])
inset_ax = zoomed_inset_axes(ax, zoom=2.5, loc='lower right')
inset_ax.axis([1.4, 1.1, 1.4, 1.1])
@image_comparison(baseline_images=['anchored_direction_arrows'],
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
extensions=['png'])
def test_anchored_direction_arrows():
fig, ax = plt.subplots()
ax.imshow(np.zeros((10, 10)))
simple_arrow = AnchoredDirectionArrows(ax.transAxes, 'X', 'Y')
ax.add_artist(simple_arrow)
@image_comparison(baseline_images=['anchored_direction_arrows_many_args'],
extensions=['png'])
def test_anchored_direction_arrows_many_args():
fig, ax = plt.subplots()
ax.imshow(np.ones((10, 10)))
direction_arrows = AnchoredDirectionArrows(
ax.transAxes, 'A', 'B', loc='upper right', color='red',
aspect_ratio=-0.5, pad=0.6, borderpad=2, frameon=True, alpha=0.7,
sep_x=-0.06, sep_y=-0.08, back_length=0.1, head_width=9,
head_length=10, tail_width=5)
ax.add_artist(direction_arrows)
def test_axes_locatable_position():
fig, ax = plt.subplots()
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad='2%')
fig.canvas.draw()
assert np.isclose(cax.get_position(original=False).width,
0.03621495327102808)
@image_comparison(baseline_images=['image_grid'], extensions=['png'],
remove_text=True, style='mpl20',
savefig_kwarg={'bbox_inches': 'tight'})
def test_image_grid():
# test that image grid works with bbox_inches=tight.
im = np.arange(100)
im.shape = 10, 10
fig = plt.figure(1, (4., 4.))
grid = ImageGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=0.1)
for i in range(4):
grid[i].imshow(im)
grid[i].set_title('test {0}{0}'.format(i))
def test_gettightbbox():
fig, ax = plt.subplots(figsize=(8, 6))
l, = ax.plot([1, 2, 3], [0, 1, 0])
ax_zoom = zoomed_inset_axes(ax, 4)
ax_zoom.plot([1, 2, 3], [0, 1, 0])
mark_inset(ax, ax_zoom, loc1=1, loc2=3, fc="none", ec='0.3')
bbox = fig.get_tightbbox(fig.canvas.get_renderer())
np.testing.assert_array_almost_equal(bbox.extents,
[-18.022743, -14.118056, 7.332813, 5.4625])
@@ -0,0 +1,142 @@
import re
import numpy as np
import pytest
from mpl_toolkits.axisartist.angle_helper import (
FormatterDMS, FormatterHMS, select_step, select_step24, select_step360)
_MS_RE = (
r'''\$ # Mathtext
(
# The sign sometimes appears on a 0 when a fraction is shown.
# Check later that there's only one.
(?P<degree_sign>-)?
(?P<degree>[0-9.]+) # Degrees value
{degree} # Degree symbol (to be replaced by format.)
)?
(
(?(degree)\\,) # Separator if degrees are also visible.
(?P<minute_sign>-)?
(?P<minute>[0-9.]+) # Minutes value
{minute} # Minute symbol (to be replaced by format.)
)?
(
(?(minute)\\,) # Separator if minutes are also visible.
(?P<second_sign>-)?
(?P<second>[0-9.]+) # Seconds value
{second} # Second symbol (to be replaced by format.)
)?
\$ # Mathtext
'''
)
DMS_RE = re.compile(_MS_RE.format(degree=re.escape(FormatterDMS.deg_mark),
minute=re.escape(FormatterDMS.min_mark),
second=re.escape(FormatterDMS.sec_mark)),
re.VERBOSE)
HMS_RE = re.compile(_MS_RE.format(degree=re.escape(FormatterHMS.deg_mark),
minute=re.escape(FormatterHMS.min_mark),
second=re.escape(FormatterHMS.sec_mark)),
re.VERBOSE)
def dms2float(degrees, minutes=0, seconds=0):
return degrees + minutes / 60.0 + seconds / 3600.0
@pytest.mark.parametrize('args, kwargs, expected_levels, expected_factor', [
((-180, 180, 10), {'hour': False}, np.arange(-180, 181, 30), 1.0),
((-12, 12, 10), {'hour': True}, np.arange(-12, 13, 2), 1.0)
])
def test_select_step(args, kwargs, expected_levels, expected_factor):
levels, n, factor = select_step(*args, **kwargs)
assert n == len(levels)
np.testing.assert_array_equal(levels, expected_levels)
assert factor == expected_factor
@pytest.mark.parametrize('args, kwargs, expected_levels, expected_factor', [
((-180, 180, 10), {}, np.arange(-180, 181, 30), 1.0),
((-12, 12, 10), {}, np.arange(-750, 751, 150), 60.0)
])
def test_select_step24(args, kwargs, expected_levels, expected_factor):
levels, n, factor = select_step24(*args, **kwargs)
assert n == len(levels)
np.testing.assert_array_equal(levels, expected_levels)
assert factor == expected_factor
@pytest.mark.parametrize('args, kwargs, expected_levels, expected_factor', [
((dms2float(20, 21.2), dms2float(21, 33.3), 5), {},
np.arange(1215, 1306, 15), 60.0),
((dms2float(20.5, seconds=21.2), dms2float(20.5, seconds=33.3), 5), {},
np.arange(73820, 73835, 2), 3600.0),
((dms2float(20, 21.2), dms2float(20, 53.3), 5), {},
np.arange(1220, 1256, 5), 60.0),
((21.2, 33.3, 5), {},
np.arange(20, 35, 2), 1.0),
((dms2float(20, 21.2), dms2float(21, 33.3), 5), {},
np.arange(1215, 1306, 15), 60.0),
((dms2float(20.5, seconds=21.2), dms2float(20.5, seconds=33.3), 5), {},
np.arange(73820, 73835, 2), 3600.0),
((dms2float(20.5, seconds=21.2), dms2float(20.5, seconds=21.4), 5), {},
np.arange(7382120, 7382141, 5), 360000.0),
# test threshold factor
((dms2float(20.5, seconds=11.2), dms2float(20.5, seconds=53.3), 5),
{'threshold_factor': 60}, np.arange(12301, 12310), 600.0),
((dms2float(20.5, seconds=11.2), dms2float(20.5, seconds=53.3), 5),
{'threshold_factor': 1}, np.arange(20502, 20517, 2), 1000.0),
])
def test_select_step360(args, kwargs, expected_levels, expected_factor):
levels, n, factor = select_step360(*args, **kwargs)
assert n == len(levels)
np.testing.assert_array_equal(levels, expected_levels)
assert factor == expected_factor
@pytest.mark.parametrize('Formatter, regex',
[(FormatterDMS, DMS_RE),
(FormatterHMS, HMS_RE)],
ids=['Degree/Minute/Second', 'Hour/Minute/Second'])
@pytest.mark.parametrize('direction, factor, values', [
("left", 60, [0, -30, -60]),
("left", 600, [12301, 12302, 12303]),
("left", 3600, [0, -30, -60]),
("left", 36000, [738210, 738215, 738220]),
("left", 360000, [7382120, 7382125, 7382130]),
("left", 1., [45, 46, 47]),
("left", 10., [452, 453, 454]),
])
def test_formatters(Formatter, regex, direction, factor, values):
fmt = Formatter()
result = fmt(direction, factor, values)
prev_degree = prev_minute = prev_second = None
for tick, value in zip(result, values):
m = regex.match(tick)
assert m is not None, '"%s" is not an expected tick format.' % (tick, )
sign = sum(m.group(sign + '_sign') is not None
for sign in ('degree', 'minute', 'second'))
assert sign <= 1, \
'Only one element of tick "%s" may have a sign.' % (tick, )
sign = 1 if sign == 0 else -1
degree = float(m.group('degree') or prev_degree or 0)
minute = float(m.group('minute') or prev_minute or 0)
second = float(m.group('second') or prev_second or 0)
if Formatter == FormatterHMS:
# 360 degrees as plot range -> 24 hours as labelled range
expected_value = pytest.approx((value // 15) / factor)
else:
expected_value = pytest.approx(value / factor)
assert sign * dms2float(degree, minute, second) == expected_value, \
'"%s" does not match expected tick value.' % (tick, )
prev_degree = degree
prev_minute = minute
prev_second = second
@@ -0,0 +1,94 @@
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
from mpl_toolkits.axisartist import AxisArtistHelperRectlinear
from mpl_toolkits.axisartist.axis_artist import (AxisArtist, AxisLabel,
LabelBase, Ticks, TickLabels)
@image_comparison(baseline_images=['axis_artist_ticks'],
extensions=['png'], style='default')
def test_ticks():
fig, ax = plt.subplots()
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
locs_angles = [((i / 10, 0.0), i * 30) for i in range(-1, 12)]
ticks_in = Ticks(ticksize=10, axis=ax.xaxis)
ticks_in.set_locs_angles(locs_angles)
ax.add_artist(ticks_in)
ticks_out = Ticks(ticksize=10, tick_out=True, color='C3', axis=ax.xaxis)
ticks_out.set_locs_angles(locs_angles)
ax.add_artist(ticks_out)
@image_comparison(baseline_images=['axis_artist_labelbase'],
extensions=['png'], style='default')
def test_labelbase():
fig, ax = plt.subplots()
ax.plot([0.5], [0.5], "o")
label = LabelBase(0.5, 0.5, "Test")
label._set_ref_angle(-90)
label._set_offset_radius(offset_radius=50)
label.set_rotation(-90)
label.set(ha="center", va="top")
ax.add_artist(label)
@image_comparison(baseline_images=['axis_artist_ticklabels'],
extensions=['png'], style='default')
def test_ticklabels():
fig, ax = plt.subplots()
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.plot([0.2, 0.4], [0.5, 0.5], "o")
ticks = Ticks(ticksize=10, axis=ax.xaxis)
ax.add_artist(ticks)
locs_angles_labels = [((0.2, 0.5), -90, "0.2"),
((0.4, 0.5), -120, "0.4")]
tick_locs_angles = [(xy, a + 180) for xy, a, l in locs_angles_labels]
ticks.set_locs_angles(tick_locs_angles)
ticklabels = TickLabels(axis_direction="left")
ticklabels._locs_angles_labels = locs_angles_labels
ticklabels.set_pad(10)
ax.add_artist(ticklabels)
ax.plot([0.5], [0.5], "s")
axislabel = AxisLabel(0.5, 0.5, "Test")
axislabel._set_offset_radius(20)
axislabel._set_ref_angle(0)
axislabel.set_axis_direction("bottom")
ax.add_artist(axislabel)
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
@image_comparison(baseline_images=['axis_artist'],
extensions=['png'], style='default')
def test_axis_artist():
fig, ax = plt.subplots()
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
for loc in ('left', 'right', 'bottom'):
_helper = AxisArtistHelperRectlinear.Fixed(ax, loc=loc)
axisline = AxisArtist(ax, _helper, offset=None, axis_direction=loc)
ax.add_artist(axisline)
# Settings for bottom AxisArtist.
axisline.set_label("TTT")
axisline.major_ticks.set_tick_out(False)
axisline.label.set_pad(5)
ax.set_ylabel("Test")
@@ -0,0 +1,89 @@
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
from matplotlib.transforms import IdentityTransform
from mpl_toolkits.axisartist.axislines import SubplotZero, Subplot
from mpl_toolkits.axisartist import SubplotHost, ParasiteAxesAuxTrans
from mpl_toolkits.axisartist import Axes
@image_comparison(baseline_images=['SubplotZero'],
extensions=['png'], style='default')
def test_SubplotZero():
fig = plt.figure()
ax = SubplotZero(fig, 1, 1, 1)
fig.add_subplot(ax)
ax.axis["xzero"].set_visible(True)
ax.axis["xzero"].label.set_text("Axis Zero")
for n in ["top", "right"]:
ax.axis[n].set_visible(False)
xx = np.arange(0, 2 * np.pi, 0.01)
ax.plot(xx, np.sin(xx))
ax.set_ylabel("Test")
@image_comparison(baseline_images=['Subplot'],
extensions=['png'], style='default')
def test_Subplot():
fig = plt.figure()
ax = Subplot(fig, 1, 1, 1)
fig.add_subplot(ax)
xx = np.arange(0, 2 * np.pi, 0.01)
ax.plot(xx, np.sin(xx))
ax.set_ylabel("Test")
ax.axis["top"].major_ticks.set_tick_out(True)
ax.axis["bottom"].major_ticks.set_tick_out(True)
ax.axis["bottom"].set_label("Tk0")
def test_Axes():
fig = plt.figure()
ax = Axes(fig, [0.15, 0.1, 0.65, 0.8])
fig.add_axes(ax)
ax.plot([1, 2, 3], [0, 1, 2])
ax.set_xscale('log')
plt.show()
@image_comparison(baseline_images=['ParasiteAxesAuxTrans_meshplot'],
extensions=['png'], remove_text=True, style='default',
tol=0.075)
def test_ParasiteAxesAuxTrans():
data = np.ones((6, 6))
data[2, 2] = 2
data[0, :] = 0
data[-2, :] = 0
data[:, 0] = 0
data[:, -2] = 0
x = np.arange(6)
y = np.arange(6)
xx, yy = np.meshgrid(x, y)
funcnames = ['pcolor', 'pcolormesh', 'contourf']
fig = plt.figure()
for i, name in enumerate(funcnames):
ax1 = SubplotHost(fig, 1, 3, i+1)
fig.add_subplot(ax1)
ax2 = ParasiteAxesAuxTrans(ax1, IdentityTransform())
ax1.parasites.append(ax2)
getattr(ax2, name)(xx, yy, data)
ax1.set_xlim((0, 5))
ax1.set_ylim((0, 5))
ax2.contour(xx, yy, data, colors='k')
@@ -0,0 +1,33 @@
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison
from matplotlib.transforms import Bbox
from mpl_toolkits.axisartist.clip_path import clip_line_to_rect
@image_comparison(baseline_images=['clip_path'],
extensions=['png'], style='default')
def test_clip_path():
x = np.array([-3, -2, -1, 0., 1, 2, 3, 2, 1, 0, -1, -2, -3, 5])
y = np.arange(len(x))
fig, ax = plt.subplots()
ax.plot(x, y, lw=1)
bbox = Bbox.from_extents(-2, 3, 2, 12.5)
rect = plt.Rectangle(bbox.p0, bbox.width, bbox.height,
facecolor='none', edgecolor='k', ls='--')
ax.add_patch(rect)
clipped_lines, ticks = clip_line_to_rect(x, y, bbox)
for lx, ly in clipped_lines:
ax.plot(lx, ly, lw=1, color='C1')
for px, py in zip(lx, ly):
assert bbox.contains(px, py)
ccc = iter(['C3o', 'C2x', 'C3o', 'C2x'])
for ttt in ticks:
cc = next(ccc)
for (xx, yy), aa in ttt:
ax.plot([xx], [yy], cc)
@@ -0,0 +1,123 @@
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.projections as mprojections
import matplotlib.transforms as mtransforms
from matplotlib.testing.decorators import image_comparison
from mpl_toolkits.axisartist.axislines import Subplot
from mpl_toolkits.axisartist.floating_axes import (
FloatingSubplot,
GridHelperCurveLinear)
from mpl_toolkits.axisartist.grid_finder import FixedLocator
from mpl_toolkits.axisartist import angle_helper
def test_subplot():
fig = plt.figure(figsize=(5, 5))
fig.clf()
ax = Subplot(fig, 111)
fig.add_subplot(ax)
@image_comparison(baseline_images=['curvelinear3'],
extensions=['png'], style='default', tol=0.01)
def test_curvelinear3():
fig = plt.figure(figsize=(5, 5))
fig.clf()
tr = (mtransforms.Affine2D().scale(np.pi / 180, 1) +
mprojections.PolarAxes.PolarTransform())
grid_locator1 = angle_helper.LocatorDMS(15)
tick_formatter1 = angle_helper.FormatterDMS()
grid_locator2 = FixedLocator([2, 4, 6, 8, 10])
grid_helper = GridHelperCurveLinear(tr,
extremes=(0, 360, 10, 3),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=None)
ax1 = FloatingSubplot(fig, 111, grid_helper=grid_helper)
fig.add_subplot(ax1)
r_scale = 10
tr2 = mtransforms.Affine2D().scale(1, 1 / r_scale) + tr
grid_locator2 = FixedLocator([30, 60, 90])
grid_helper2 = GridHelperCurveLinear(tr2,
extremes=(0, 360,
10 * r_scale, 3 * r_scale),
grid_locator2=grid_locator2)
ax1.axis["right"] = axis = grid_helper2.new_fixed_axis("right", axes=ax1)
ax1.axis["left"].label.set_text("Test 1")
ax1.axis["right"].label.set_text("Test 2")
for an in ["left", "right"]:
ax1.axis[an].set_visible(False)
axis = grid_helper.new_floating_axis(1, 7, axes=ax1,
axis_direction="bottom")
ax1.axis["z"] = axis
axis.toggle(all=True, label=True)
axis.label.set_text("z = ?")
axis.label.set_visible(True)
axis.line.set_color("0.5")
ax2 = ax1.get_aux_axes(tr)
xx, yy = [67, 90, 75, 30], [2, 5, 8, 4]
ax2.scatter(xx, yy)
l, = ax2.plot(xx, yy, "k-")
l.set_clip_path(ax1.patch)
@image_comparison(baseline_images=['curvelinear4'],
extensions=['png'], style='default', tol=0.015)
def test_curvelinear4():
fig = plt.figure(figsize=(5, 5))
fig.clf()
tr = (mtransforms.Affine2D().scale(np.pi / 180, 1) +
mprojections.PolarAxes.PolarTransform())
grid_locator1 = angle_helper.LocatorDMS(5)
tick_formatter1 = angle_helper.FormatterDMS()
grid_locator2 = FixedLocator([2, 4, 6, 8, 10])
grid_helper = GridHelperCurveLinear(tr,
extremes=(120, 30, 10, 0),
grid_locator1=grid_locator1,
grid_locator2=grid_locator2,
tick_formatter1=tick_formatter1,
tick_formatter2=None)
ax1 = FloatingSubplot(fig, 111, grid_helper=grid_helper)
fig.add_subplot(ax1)
ax1.axis["left"].label.set_text("Test 1")
ax1.axis["right"].label.set_text("Test 2")
for an in ["top"]:
ax1.axis[an].set_visible(False)
axis = grid_helper.new_floating_axis(1, 70, axes=ax1,
axis_direction="bottom")
ax1.axis["z"] = axis
axis.toggle(all=True, label=True)
axis.label.set_axis_direction("top")
axis.label.set_text("z = ?")
axis.label.set_visible(True)
axis.line.set_color("0.5")
ax2 = ax1.get_aux_axes(tr)
xx, yy = [67, 90, 75, 30], [2, 5, 8, 4]
ax2.scatter(xx, yy)
l, = ax2.plot(xx, yy, "k-")
l.set_clip_path(ax1.patch)
@@ -0,0 +1,13 @@
from mpl_toolkits.axisartist.grid_finder import (
FormatterPrettyPrint,
MaxNLocator)
def test_pretty_print_format():
locator = MaxNLocator()
locs, nloc, factor = locator(0, 100)
fmt = FormatterPrettyPrint()
assert fmt("left", None, locs) == \
[r'$\mathdefault{%d}$' % (l, ) for l in locs]
@@ -0,0 +1,214 @@
import numpy as np
import platform
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.projections import PolarAxes
from matplotlib.transforms import Affine2D, Transform
from matplotlib.testing.decorators import image_comparison
from mpl_toolkits.axes_grid1.parasite_axes import ParasiteAxesAuxTrans
from mpl_toolkits.axisartist import SubplotHost
from mpl_toolkits.axes_grid1.parasite_axes import host_subplot_class_factory
from mpl_toolkits.axisartist import angle_helper
from mpl_toolkits.axisartist.axislines import Axes
from mpl_toolkits.axisartist.grid_helper_curvelinear import \
GridHelperCurveLinear
@image_comparison(baseline_images=['custom_transform'],
extensions=['png'], style='default', tol=0.03)
def test_custom_transform():
class MyTransform(Transform):
input_dims = 2
output_dims = 2
is_separable = False
def __init__(self, resolution):
"""
Resolution is the number of steps to interpolate between each input
line segment to approximate its path in transformed space.
"""
Transform.__init__(self)
self._resolution = resolution
def transform(self, ll):
x, y = ll.T
return np.column_stack([x, y - x])
transform_non_affine = transform
def transform_path(self, path):
vertices = path.vertices
ipath = path.interpolated(self._resolution)
return Path(self.transform(ipath.vertices), ipath.codes)
transform_path_non_affine = transform_path
def inverted(self):
return MyTransformInv(self._resolution)
class MyTransformInv(Transform):
input_dims = 2
output_dims = 2
is_separable = False
def __init__(self, resolution):
Transform.__init__(self)
self._resolution = resolution
def transform(self, ll):
x, y = ll.T
return np.column_stack([x, y + x])
def inverted(self):
return MyTransform(self._resolution)
fig = plt.figure()
SubplotHost = host_subplot_class_factory(Axes)
tr = MyTransform(1)
grid_helper = GridHelperCurveLinear(tr)
ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)
fig.add_subplot(ax1)
ax2 = ParasiteAxesAuxTrans(ax1, tr, "equal")
ax1.parasites.append(ax2)
ax2.plot([3, 6], [5.0, 10.])
ax1.set_aspect(1.)
ax1.set_xlim(0, 10)
ax1.set_ylim(0, 10)
ax1.grid(True)
@image_comparison(baseline_images=['polar_box'],
tol={'aarch64': 0.04}.get(platform.machine(), 0.03),
extensions=['png'], style='default')
def test_polar_box():
fig = plt.figure(figsize=(5, 5))
# PolarAxes.PolarTransform takes radian. However, we want our coordinate
# system in degree
tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform()
# polar projection, which involves cycle, and also has limits in
# its coordinates, needs a special method to find the extremes
# (min, max of the coordinate within the view).
extreme_finder = angle_helper.ExtremeFinderCycle(20, 20,
lon_cycle=360,
lat_cycle=None,
lon_minmax=None,
lat_minmax=(0, np.inf))
grid_locator1 = angle_helper.LocatorDMS(12)
tick_formatter1 = angle_helper.FormatterDMS()
grid_helper = GridHelperCurveLinear(tr,
extreme_finder=extreme_finder,
grid_locator1=grid_locator1,
tick_formatter1=tick_formatter1)
ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)
ax1.axis["right"].major_ticklabels.set_visible(True)
ax1.axis["top"].major_ticklabels.set_visible(True)
# let right axis shows ticklabels for 1st coordinate (angle)
ax1.axis["right"].get_helper().nth_coord_ticks = 0
# let bottom axis shows ticklabels for 2nd coordinate (radius)
ax1.axis["bottom"].get_helper().nth_coord_ticks = 1
fig.add_subplot(ax1)
ax1.axis["lat"] = axis = grid_helper.new_floating_axis(0, 45, axes=ax1)
axis.label.set_text("Test")
axis.label.set_visible(True)
axis.get_helper()._extremes = 2, 12
ax1.axis["lon"] = axis = grid_helper.new_floating_axis(1, 6, axes=ax1)
axis.label.set_text("Test 2")
axis.get_helper()._extremes = -180, 90
# A parasite axes with given transform
ax2 = ParasiteAxesAuxTrans(ax1, tr, "equal")
assert ax2.transData == tr + ax1.transData
# Anything you draw in ax2 will match the ticks and grids of ax1.
ax1.parasites.append(ax2)
ax2.plot(np.linspace(0, 30, 50), np.linspace(10, 10, 50))
ax1.set_aspect(1.)
ax1.set_xlim(-5, 12)
ax1.set_ylim(-5, 10)
ax1.grid(True)
@image_comparison(baseline_images=['axis_direction'],
extensions=['png'], style='default', tol=0.03)
def test_axis_direction():
fig = plt.figure(figsize=(5, 5))
# PolarAxes.PolarTransform takes radian. However, we want our coordinate
# system in degree
tr = Affine2D().scale(np.pi / 180., 1.) + PolarAxes.PolarTransform()
# polar projection, which involves cycle, and also has limits in
# its coordinates, needs a special method to find the extremes
# (min, max of the coordinate within the view).
# 20, 20 : number of sampling points along x, y direction
extreme_finder = angle_helper.ExtremeFinderCycle(20, 20,
lon_cycle=360,
lat_cycle=None,
lon_minmax=None,
lat_minmax=(0, np.inf),
)
grid_locator1 = angle_helper.LocatorDMS(12)
tick_formatter1 = angle_helper.FormatterDMS()
grid_helper = GridHelperCurveLinear(tr,
extreme_finder=extreme_finder,
grid_locator1=grid_locator1,
tick_formatter1=tick_formatter1)
ax1 = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)
for axis in ax1.axis.values():
axis.set_visible(False)
fig.add_subplot(ax1)
ax1.axis["lat1"] = axis = grid_helper.new_floating_axis(
0, 130,
axes=ax1, axis_direction="left")
axis.label.set_text("Test")
axis.label.set_visible(True)
axis.get_helper()._extremes = 0.001, 10
ax1.axis["lat2"] = axis = grid_helper.new_floating_axis(
0, 50,
axes=ax1, axis_direction="right")
axis.label.set_text("Test")
axis.label.set_visible(True)
axis.get_helper()._extremes = 0.001, 10
ax1.axis["lon"] = axis = grid_helper.new_floating_axis(
1, 10,
axes=ax1, axis_direction="bottom")
axis.label.set_text("Test 2")
axis.get_helper()._extremes = 50, 130
axis.major_ticklabels.set_axis_direction("top")
axis.label.set_axis_direction("top")
grid_helper.grid_finder.grid_locator1.den = 5
grid_helper.grid_finder.grid_locator2._nbins = 5
ax1.set_aspect(1.)
ax1.set_xlim(-8, 8)
ax1.set_ylim(-4, 12)
ax1.grid(True)
@@ -0,0 +1,814 @@
import pytest
from mpl_toolkits.mplot3d import Axes3D, axes3d, proj3d, art3d
from matplotlib import cm
from matplotlib.testing.decorators import image_comparison, check_figures_equal
from matplotlib.collections import LineCollection
from matplotlib.patches import Circle
import matplotlib.pyplot as plt
import numpy as np
@image_comparison(baseline_images=['bar3d'], remove_text=True)
def test_bar3d():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for c, z in zip(['r', 'g', 'b', 'y'], [30, 20, 10, 0]):
xs = np.arange(20)
ys = np.arange(20)
cs = [c] * len(xs)
cs[0] = 'c'
ax.bar(xs, ys, zs=z, zdir='y', align='edge', color=cs, alpha=0.8)
@image_comparison(
baseline_images=['bar3d_shaded'],
remove_text=True,
extensions=['png']
)
def test_bar3d_shaded():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.arange(4)
y = np.arange(5)
x2d, y2d = np.meshgrid(x, y)
x2d, y2d = x2d.ravel(), y2d.ravel()
z = x2d + y2d
ax.bar3d(x2d, y2d, x2d * 0, 1, 1, z, shade=True)
fig.canvas.draw()
@image_comparison(
baseline_images=['bar3d_notshaded'],
remove_text=True,
extensions=['png']
)
def test_bar3d_notshaded():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.arange(4)
y = np.arange(5)
x2d, y2d = np.meshgrid(x, y)
x2d, y2d = x2d.ravel(), y2d.ravel()
z = x2d + y2d
ax.bar3d(x2d, y2d, x2d * 0, 1, 1, z, shade=False)
fig.canvas.draw()
@image_comparison(baseline_images=['contour3d'],
remove_text=True, style='mpl20')
def test_contour3d():
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
cset = ax.contour(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)
cset = ax.contour(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)
ax.set_xlim(-40, 40)
ax.set_ylim(-40, 40)
ax.set_zlim(-100, 100)
@image_comparison(baseline_images=['contourf3d'], remove_text=True)
def test_contourf3d():
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
cset = ax.contourf(X, Y, Z, zdir='z', offset=-100, cmap=cm.coolwarm)
cset = ax.contourf(X, Y, Z, zdir='x', offset=-40, cmap=cm.coolwarm)
cset = ax.contourf(X, Y, Z, zdir='y', offset=40, cmap=cm.coolwarm)
ax.set_xlim(-40, 40)
ax.set_ylim(-40, 40)
ax.set_zlim(-100, 100)
@image_comparison(baseline_images=['contourf3d_fill'], remove_text=True)
def test_contourf3d_fill():
fig = plt.figure()
ax = fig.gca(projection='3d')
X, Y = np.meshgrid(np.arange(-2, 2, 0.25), np.arange(-2, 2, 0.25))
Z = X.clip(0, 0)
# This produces holes in the z=0 surface that causes rendering errors if
# the Poly3DCollection is not aware of path code information (issue #4784)
Z[::5, ::5] = 0.1
cset = ax.contourf(X, Y, Z, offset=0, levels=[-0.1, 0], cmap=cm.coolwarm)
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)
ax.set_zlim(-1, 1)
@image_comparison(baseline_images=['tricontour'], remove_text=True,
style='mpl20', extensions=['png'])
def test_tricontour():
fig = plt.figure()
np.random.seed(19680801)
x = np.random.rand(1000) - 0.5
y = np.random.rand(1000) - 0.5
z = -(x**2 + y**2)
ax = fig.add_subplot(1, 2, 1, projection='3d')
ax.tricontour(x, y, z)
ax = fig.add_subplot(1, 2, 2, projection='3d')
ax.tricontourf(x, y, z)
@image_comparison(baseline_images=['lines3d'], remove_text=True)
def test_lines3d():
fig = plt.figure()
ax = fig.gca(projection='3d')
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z ** 2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
ax.plot(x, y, z)
# Reason for flakiness of SVG test is still unknown.
@image_comparison(baseline_images=['mixedsubplot'], remove_text=True,
extensions=['png', 'pdf',
pytest.mark.xfail('svg', strict=False)])
def test_mixedsubplots():
def f(t):
s1 = np.cos(2*np.pi*t)
e1 = np.exp(-t)
return np.multiply(s1, e1)
t1 = np.arange(0.0, 5.0, 0.1)
t2 = np.arange(0.0, 5.0, 0.02)
fig = plt.figure(figsize=plt.figaspect(2.))
ax = fig.add_subplot(2, 1, 1)
l = ax.plot(t1, f(t1), 'bo',
t2, f(t2), 'k--', markerfacecolor='green')
ax.grid(True)
ax = fig.add_subplot(2, 1, 2, projection='3d')
X, Y = np.meshgrid(np.arange(-5, 5, 0.25), np.arange(-5, 5, 0.25))
R = np.sqrt(X ** 2 + Y ** 2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rcount=40, ccount=40,
linewidth=0, antialiased=False)
ax.set_zlim3d(-1, 1)
@check_figures_equal(extensions=['png'])
def test_tight_layout_text(fig_test, fig_ref):
# text is currently ignored in tight layout. So the order of text() and
# tight_layout() calls should not influence the result.
ax1 = fig_test.gca(projection='3d')
ax1.text(.5, .5, .5, s='some string')
fig_test.tight_layout()
ax2 = fig_ref.gca(projection='3d')
fig_ref.tight_layout()
ax2.text(.5, .5, .5, s='some string')
@image_comparison(baseline_images=['scatter3d'], remove_text=True)
def test_scatter3d():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(np.arange(10), np.arange(10), np.arange(10),
c='r', marker='o')
ax.scatter(np.arange(10, 20), np.arange(10, 20), np.arange(10, 20),
c='b', marker='^')
@image_comparison(baseline_images=['scatter3d_color'], remove_text=True,
extensions=['png'])
def test_scatter3d_color():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(np.arange(10), np.arange(10), np.arange(10),
color='r', marker='o')
ax.scatter(np.arange(10, 20), np.arange(10, 20), np.arange(10, 20),
color='b', marker='s')
@image_comparison(baseline_images=['plot_3d_from_2d'], remove_text=True,
extensions=['png'])
def test_plot_3d_from_2d():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
xs = np.arange(0, 5)
ys = np.arange(5, 10)
ax.plot(xs, ys, zs=0, zdir='x')
ax.plot(xs, ys, zs=0, zdir='y')
@image_comparison(baseline_images=['surface3d'], remove_text=True)
def test_surface3d():
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X ** 2 + Y ** 2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rcount=40, ccount=40, cmap=cm.coolwarm,
lw=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)
fig.colorbar(surf, shrink=0.5, aspect=5)
@image_comparison(baseline_images=['surface3d_shaded'], remove_text=True,
extensions=['png'])
def test_surface3d_shaded():
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X ** 2 + Y ** 2)
Z = np.sin(R)
surf = ax.plot_surface(X, Y, Z, rstride=5, cstride=5,
color=[0.25, 1, 0.25], lw=1, antialiased=False)
ax.set_zlim(-1.01, 1.01)
@image_comparison(baseline_images=['text3d'])
def test_text3d():
fig = plt.figure()
ax = fig.gca(projection='3d')
zdirs = (None, 'x', 'y', 'z', (1, 1, 0), (1, 1, 1))
xs = (2, 6, 4, 9, 7, 2)
ys = (6, 4, 8, 7, 2, 2)
zs = (4, 2, 5, 6, 1, 7)
for zdir, x, y, z in zip(zdirs, xs, ys, zs):
label = '(%d, %d, %d), dir=%s' % (x, y, z, zdir)
ax.text(x, y, z, label, zdir)
ax.text(1, 1, 1, "red", color='red')
ax.text2D(0.05, 0.95, "2D Text", transform=ax.transAxes)
ax.set_xlim3d(0, 10)
ax.set_ylim3d(0, 10)
ax.set_zlim3d(0, 10)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
@image_comparison(baseline_images=['trisurf3d'], remove_text=True, tol=0.03)
def test_trisurf3d():
n_angles = 36
n_radii = 8
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
angles[:, 1::2] += np.pi/n_angles
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2)
@image_comparison(baseline_images=['trisurf3d_shaded'], remove_text=True,
tol=0.03, extensions=['png'])
def test_trisurf3d_shaded():
n_angles = 36
n_radii = 8
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
angles[:, 1::2] += np.pi/n_angles
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, color=[1, 0.5, 0], linewidth=0.2)
@image_comparison(baseline_images=['wireframe3d'], remove_text=True)
def test_wireframe3d():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_wireframe(X, Y, Z, rcount=13, ccount=13)
@image_comparison(baseline_images=['wireframe3dzerocstride'], remove_text=True,
extensions=['png'])
def test_wireframe3dzerocstride():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_wireframe(X, Y, Z, rcount=13, ccount=0)
@image_comparison(baseline_images=['wireframe3dzerorstride'], remove_text=True,
extensions=['png'])
def test_wireframe3dzerorstride():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
ax.plot_wireframe(X, Y, Z, rstride=0, cstride=10)
def test_wireframe3dzerostrideraises():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
with pytest.raises(ValueError):
ax.plot_wireframe(X, Y, Z, rstride=0, cstride=0)
def test_mixedsamplesraises():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = axes3d.get_test_data(0.05)
with pytest.raises(ValueError):
ax.plot_wireframe(X, Y, Z, rstride=10, ccount=50)
with pytest.raises(ValueError):
ax.plot_surface(X, Y, Z, cstride=50, rcount=10)
@image_comparison(baseline_images=['quiver3d'], remove_text=True)
def test_quiver3d():
fig = plt.figure()
ax = fig.gca(projection='3d')
x, y, z = np.ogrid[-1:0.8:10j, -1:0.8:10j, -1:0.6:3j]
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) *
np.sin(np.pi * z))
ax.quiver(x, y, z, u, v, w, length=0.1, pivot='tip', normalize=True)
@image_comparison(baseline_images=['quiver3d_empty'], remove_text=True)
def test_quiver3d_empty():
fig = plt.figure()
ax = fig.gca(projection='3d')
x, y, z = np.ogrid[-1:0.8:0j, -1:0.8:0j, -1:0.6:0j]
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) *
np.sin(np.pi * z))
ax.quiver(x, y, z, u, v, w, length=0.1, pivot='tip', normalize=True)
@image_comparison(baseline_images=['quiver3d_masked'], remove_text=True)
def test_quiver3d_masked():
fig = plt.figure()
ax = fig.gca(projection='3d')
# Using mgrid here instead of ogrid because masked_where doesn't
# seem to like broadcasting very much...
x, y, z = np.mgrid[-1:0.8:10j, -1:0.8:10j, -1:0.6:3j]
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) *
np.sin(np.pi * z))
u = np.ma.masked_where((-0.4 < x) & (x < 0.1), u, copy=False)
v = np.ma.masked_where((0.1 < y) & (y < 0.7), v, copy=False)
ax.quiver(x, y, z, u, v, w, length=0.1, pivot='tip', normalize=True)
@image_comparison(baseline_images=['quiver3d_pivot_middle'], remove_text=True,
extensions=['png'])
def test_quiver3d_pivot_middle():
fig = plt.figure()
ax = fig.gca(projection='3d')
x, y, z = np.ogrid[-1:0.8:10j, -1:0.8:10j, -1:0.6:3j]
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) *
np.sin(np.pi * z))
ax.quiver(x, y, z, u, v, w, length=0.1, pivot='middle', normalize=True)
@image_comparison(baseline_images=['quiver3d_pivot_tail'], remove_text=True,
extensions=['png'])
def test_quiver3d_pivot_tail():
fig = plt.figure()
ax = fig.gca(projection='3d')
x, y, z = np.ogrid[-1:0.8:10j, -1:0.8:10j, -1:0.6:3j]
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) *
np.sin(np.pi * z))
ax.quiver(x, y, z, u, v, w, length=0.1, pivot='tail', normalize=True)
@image_comparison(baseline_images=['poly3dcollection_closed'],
remove_text=True)
def test_poly3dcollection_closed():
fig = plt.figure()
ax = fig.gca(projection='3d')
poly1 = np.array([[0, 0, 1], [0, 1, 1], [0, 0, 0]], float)
poly2 = np.array([[0, 1, 1], [1, 1, 1], [1, 1, 0]], float)
c1 = art3d.Poly3DCollection([poly1], linewidths=3, edgecolor='k',
facecolor=(0.5, 0.5, 1, 0.5), closed=True)
c2 = art3d.Poly3DCollection([poly2], linewidths=3, edgecolor='k',
facecolor=(1, 0.5, 0.5, 0.5), closed=False)
ax.add_collection3d(c1)
ax.add_collection3d(c2)
@image_comparison(baseline_images=['axes3d_labelpad'], extensions=['png'])
def test_axes3d_labelpad():
from matplotlib import rcParams
fig = plt.figure()
ax = Axes3D(fig)
# labelpad respects rcParams
assert ax.xaxis.labelpad == rcParams['axes.labelpad']
# labelpad can be set in set_label
ax.set_xlabel('X LABEL', labelpad=10)
assert ax.xaxis.labelpad == 10
ax.set_ylabel('Y LABEL')
ax.set_zlabel('Z LABEL')
# or manually
ax.yaxis.labelpad = 20
ax.zaxis.labelpad = -40
# Tick labels also respect tick.pad (also from rcParams)
for i, tick in enumerate(ax.yaxis.get_major_ticks()):
tick.set_pad(tick.get_pad() - i * 5)
@image_comparison(baseline_images=['axes3d_cla'], extensions=['png'])
def test_axes3d_cla():
# fixed in pull request 4553
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
ax.set_axis_off()
ax.cla() # make sure the axis displayed is 3D (not 2D)
def test_plotsurface_1d_raises():
x = np.linspace(0.5, 10, num=100)
y = np.linspace(0.5, 10, num=100)
X, Y = np.meshgrid(x, y)
z = np.random.randn(100)
fig = plt.figure(figsize=(14,6))
ax = fig.add_subplot(1, 2, 1, projection='3d')
with pytest.raises(ValueError):
ax.plot_surface(X, Y, z)
def _test_proj_make_M():
# eye point
E = np.array([1000, -1000, 2000])
R = np.array([100, 100, 100])
V = np.array([0, 0, 1])
viewM = proj3d.view_transformation(E, R, V)
perspM = proj3d.persp_transformation(100, -100)
M = np.dot(perspM, viewM)
return M
def test_proj_transform():
M = _test_proj_make_M()
xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 300.0
ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 300.0
zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 300.0
txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M)
ixs, iys, izs = proj3d.inv_transform(txs, tys, tzs, M)
np.testing.assert_almost_equal(ixs, xs)
np.testing.assert_almost_equal(iys, ys)
np.testing.assert_almost_equal(izs, zs)
def _test_proj_draw_axes(M, s=1, *args, **kwargs):
xs = [0, s, 0, 0]
ys = [0, 0, s, 0]
zs = [0, 0, 0, s]
txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M)
o, ax, ay, az = zip(txs, tys)
lines = [(o, ax), (o, ay), (o, az)]
fig, ax = plt.subplots(*args, **kwargs)
linec = LineCollection(lines)
ax.add_collection(linec)
for x, y, t in zip(txs, tys, ['o', 'x', 'y', 'z']):
ax.text(x, y, t)
return fig, ax
@image_comparison(baseline_images=['proj3d_axes_cube'], extensions=['png'],
remove_text=True, style='default')
def test_proj_axes_cube():
M = _test_proj_make_M()
ts = '0 1 2 3 0 4 5 6 7 4'.split()
xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 300.0
ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 300.0
zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 300.0
txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M)
fig, ax = _test_proj_draw_axes(M, s=400)
ax.scatter(txs, tys, c=tzs)
ax.plot(txs, tys, c='r')
for x, y, t in zip(txs, tys, ts):
ax.text(x, y, t)
ax.set_xlim(-0.2, 0.2)
ax.set_ylim(-0.2, 0.2)
@image_comparison(baseline_images=['proj3d_axes_cube_ortho'],
extensions=['png'], remove_text=True, style='default')
def test_proj_axes_cube_ortho():
E = np.array([200, 100, 100])
R = np.array([0, 0, 0])
V = np.array([0, 0, 1])
viewM = proj3d.view_transformation(E, R, V)
orthoM = proj3d.ortho_transformation(-1, 1)
M = np.dot(orthoM, viewM)
ts = '0 1 2 3 0 4 5 6 7 4'.split()
xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 100
ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 100
zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 100
txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M)
fig, ax = _test_proj_draw_axes(M, s=150)
ax.scatter(txs, tys, s=300-tzs)
ax.plot(txs, tys, c='r')
for x, y, t in zip(txs, tys, ts):
ax.text(x, y, t)
ax.set_xlim(-200, 200)
ax.set_ylim(-200, 200)
def test_rot():
V = [1, 0, 0, 1]
rotated_V = proj3d.rot_x(V, np.pi / 6)
np.testing.assert_allclose(rotated_V, [1, 0, 0, 1])
V = [0, 1, 0, 1]
rotated_V = proj3d.rot_x(V, np.pi / 6)
np.testing.assert_allclose(rotated_V, [0, np.sqrt(3) / 2, 0.5, 1])
def test_world():
xmin, xmax = 100, 120
ymin, ymax = -100, 100
zmin, zmax = 0.1, 0.2
M = proj3d.world_transformation(xmin, xmax, ymin, ymax, zmin, zmax)
np.testing.assert_allclose(M,
[[5e-2, 0, 0, -5],
[0, 5e-3, 0, 5e-1],
[0, 0, 1e1, -1],
[0, 0, 0, 1]])
@image_comparison(baseline_images=['proj3d_lines_dists'], extensions=['png'],
remove_text=True, style='default')
def test_lines_dists():
fig, ax = plt.subplots(figsize=(4, 6), subplot_kw=dict(aspect='equal'))
xs = (0, 30)
ys = (20, 150)
ax.plot(xs, ys)
p0, p1 = zip(xs, ys)
xs = (0, 0, 20, 30)
ys = (100, 150, 30, 200)
ax.scatter(xs, ys)
dist = proj3d.line2d_seg_dist(p0, p1, (xs[0], ys[0]))
dist = proj3d.line2d_seg_dist(p0, p1, np.array((xs, ys)))
for x, y, d in zip(xs, ys, dist):
c = Circle((x, y), d, fill=0)
ax.add_patch(c)
ax.set_xlim(-50, 150)
ax.set_ylim(0, 300)
def test_autoscale():
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
ax.margins(x=0, y=.1, z=.2)
ax.plot([0, 1], [0, 1], [0, 1])
assert ax.get_w_lims() == (0, 1, -.1, 1.1, -.2, 1.2)
ax.autoscale(False)
ax.set_autoscalez_on(True)
ax.plot([0, 2], [0, 2], [0, 2])
assert ax.get_w_lims() == (0, 1, -.1, 1.1, -.4, 2.4)
@image_comparison(baseline_images=['axes3d_ortho'], style='default')
def test_axes3d_ortho():
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_proj_type('ortho')
@pytest.mark.parametrize('value', [np.inf, np.nan])
@pytest.mark.parametrize(('setter', 'side'), [
('set_xlim3d', 'left'),
('set_xlim3d', 'right'),
('set_ylim3d', 'bottom'),
('set_ylim3d', 'top'),
('set_zlim3d', 'bottom'),
('set_zlim3d', 'top'),
])
def test_invalid_axes_limits(setter, side, value):
limit = {side: value}
fig = plt.figure()
obj = fig.add_subplot(111, projection='3d')
with pytest.raises(ValueError):
getattr(obj, setter)(**limit)
class TestVoxels(object):
@image_comparison(
baseline_images=['voxels-simple'],
extensions=['png'],
remove_text=True
)
def test_simple(self):
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
x, y, z = np.indices((5, 4, 3))
voxels = (x == y) | (y == z)
ax.voxels(voxels)
@image_comparison(
baseline_images=['voxels-edge-style'],
extensions=['png'],
remove_text=True,
style='default'
)
def test_edge_style(self):
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
x, y, z = np.indices((5, 5, 4))
voxels = ((x - 2)**2 + (y - 2)**2 + (z-1.5)**2) < 2.2**2
v = ax.voxels(voxels, linewidths=3, edgecolor='C1')
# change the edge color of one voxel
v[max(v.keys())].set_edgecolor('C2')
@image_comparison(
baseline_images=['voxels-named-colors'],
extensions=['png'],
remove_text=True
)
def test_named_colors(self):
""" test with colors set to a 3d object array of strings """
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
x, y, z = np.indices((10, 10, 10))
voxels = (x == y) | (y == z)
voxels = voxels & ~(x * y * z < 1)
colors = np.zeros((10, 10, 10), dtype=np.object_)
colors.fill('C0')
colors[(x < 5) & (y < 5)] = '0.25'
colors[(x + z) < 10] = 'cyan'
ax.voxels(voxels, facecolors=colors)
@image_comparison(
baseline_images=['voxels-rgb-data'],
extensions=['png'],
remove_text=True
)
def test_rgb_data(self):
""" test with colors set to a 4d float array of rgb data """
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
x, y, z = np.indices((10, 10, 10))
voxels = (x == y) | (y == z)
colors = np.zeros((10, 10, 10, 3))
colors[...,0] = x/9.0
colors[...,1] = y/9.0
colors[...,2] = z/9.0
ax.voxels(voxels, facecolors=colors)
@image_comparison(
baseline_images=['voxels-alpha'],
extensions=['png'],
remove_text=True
)
def test_alpha(self):
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
x, y, z = np.indices((10, 10, 10))
v1 = x == y
v2 = np.abs(x - y) < 2
voxels = v1 | v2
colors = np.zeros((10, 10, 10, 4))
colors[v2] = [1, 0, 0, 0.5]
colors[v1] = [0, 1, 0, 0.5]
v = ax.voxels(voxels, facecolors=colors)
assert type(v) is dict
for coord, poly in v.items():
assert voxels[coord], "faces returned for absent voxel"
assert isinstance(poly, art3d.Poly3DCollection)
@image_comparison(
baseline_images=['voxels-xyz'],
extensions=['png'],
tol=0.01
)
def test_xyz(self):
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
def midpoints(x):
sl = ()
for i in range(x.ndim):
x = (x[sl + np.index_exp[:-1]] +
x[sl + np.index_exp[1:]]) / 2.0
sl += np.index_exp[:]
return x
# prepare some coordinates, and attach rgb values to each
r, g, b = np.indices((17, 17, 17)) / 16.0
rc = midpoints(r)
gc = midpoints(g)
bc = midpoints(b)
# define a sphere about [0.5, 0.5, 0.5]
sphere = (rc - 0.5)**2 + (gc - 0.5)**2 + (bc - 0.5)**2 < 0.5**2
# combine the color components
colors = np.zeros(sphere.shape + (3,))
colors[..., 0] = rc
colors[..., 1] = gc
colors[..., 2] = bc
# and plot everything
ax.voxels(r, g, b, sphere,
facecolors=colors,
edgecolors=np.clip(2*colors - 0.5, 0, 1), # brighter
linewidth=0.5)
def test_calling_conventions(self):
x, y, z = np.indices((3, 4, 5))
filled = np.ones((2, 3, 4))
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
# all the valid calling conventions
for kw in (dict(), dict(edgecolor='k')):
ax.voxels(filled, **kw)
ax.voxels(filled=filled, **kw)
ax.voxels(x, y, z, filled, **kw)
ax.voxels(x, y, z, filled=filled, **kw)
# duplicate argument
with pytest.raises(TypeError) as exc:
ax.voxels(x, y, z, filled, filled=filled)
exc.match(".*voxels.*")
# missing arguments
with pytest.raises(TypeError) as exc:
ax.voxels(x, y)
exc.match(".*voxels.*")
# x,y,z are positional only - this passes them on as attributes of
# Poly3DCollection
with pytest.raises(AttributeError):
ax.voxels(filled=filled, x=x, y=y, z=z)
def test_inverted_cla():
# Github PR #5450. Setting autoscale should reset
# axes to be non-inverted.
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
# 1. test that a new axis is not inverted per default
assert not ax.xaxis_inverted()
assert not ax.yaxis_inverted()
assert not ax.zaxis_inverted()
ax.set_xlim(1, 0)
ax.set_ylim(1, 0)
ax.set_zlim(1, 0)
assert ax.xaxis_inverted()
assert ax.yaxis_inverted()
assert ax.zaxis_inverted()
ax.cla()
assert not ax.xaxis_inverted()
assert not ax.yaxis_inverted()
assert not ax.zaxis_inverted()