demo + utils venv
@@ -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
|
||||
@@ -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
|
||||
@@ -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.')
|
||||
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 9.9 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
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 |
|
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 |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
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 |
|
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 |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 51 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 |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 126 KiB |
|
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 |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
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 |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 82 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()
|
||||