aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/matplotlib/py2/mpl_toolkits
diff options
context:
space:
mode:
authorshumkovnd <shumkovnd@yandex-team.com>2023-11-10 14:39:34 +0300
committershumkovnd <shumkovnd@yandex-team.com>2023-11-10 16:42:24 +0300
commit77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch)
treec51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py2/mpl_toolkits
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
downloadydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py2/mpl_toolkits')
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/__init__.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/ChangeLog13
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/__init__.py15
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/anchored_artists.py9
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/angle_helper.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_divider.py8
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_grid.py30
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_rgb.py11
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_size.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axis_artist.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axisline_style.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axislines.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/clip_path.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/colorbar.py5
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/floating_axes.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/grid_finder.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/grid_helper_curvelinear.py4
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/inset_locator.py7
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/parasite_axes.py18
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/__init__.py12
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/anchored_artists.py376
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_divider.py975
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_grid.py771
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_rgb.py228
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_size.py323
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/colorbar.py836
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/inset_locator.py659
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/mpl_axes.py154
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/parasite_axes.py486
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/__init__.py26
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/angle_helper.py416
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_divider.py9
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_grid.py30
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_rgb.py11
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axis_artist.py1527
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axisline_style.py168
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axislines.py828
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/clip_path.py135
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/floating_axes.py544
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/grid_finder.py340
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/grid_helper_curvelinear.py475
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/axisartist/parasite_axes.py18
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/__init__.py6
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/art3d.py774
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/axes3d.py2958
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/axis3d.py484
-rw-r--r--contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/proj3d.py203
47 files changed, 13928 insertions, 0 deletions
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/__init__.py b/contrib/python/matplotlib/py2/mpl_toolkits/__init__.py
new file mode 100644
index 0000000000..8d9942e652
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/__init__.py
@@ -0,0 +1,4 @@
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ pass # must not have setuptools
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/ChangeLog b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/ChangeLog
new file mode 100644
index 0000000000..79cc01cfdf
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/ChangeLog
@@ -0,0 +1,13 @@
+2009-06-01 Jae-Joon Lee <lee.j.joon@gmail.com>
+
+ * axislines.py (Axes.toggle_axisline): fix broken spine support.
+ (AxisArtistHelper): Initial support for curvelinear grid and ticks.
+
+2009-05-04 Jae-Joon Lee <lee.j.joon@gmail.com>
+
+ * inset_locator.py (inset_axes, zoomed_inset_axes): axes_class support
+
+ * axislines.py : Better support for tick (tick label) color
+ handling
+ (Axes.get_children): fix typo
+
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/__init__.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/__init__.py
new file mode 100644
index 0000000000..c10e89bd62
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/__init__.py
@@ -0,0 +1,15 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+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 provies the same'
+ ' functionality',
+ obj_type='module')
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/anchored_artists.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/anchored_artists.py
new file mode 100644
index 0000000000..14b661497d
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/anchored_artists.py
@@ -0,0 +1,9 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from matplotlib.offsetbox import AnchoredOffsetbox, AuxTransformBox, VPacker,\
+ TextArea, AnchoredText, DrawingArea, AnnotationBbox
+
+from mpl_toolkits.axes_grid1.anchored_artists import \
+ AnchoredDrawingArea, AnchoredAuxTransformBox, \
+ AnchoredEllipse, AnchoredSizeBar
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/angle_helper.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/angle_helper.py
new file mode 100644
index 0000000000..f0f877d913
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/angle_helper.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axisartist.angle_helper import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_divider.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_divider.py
new file mode 100644
index 0000000000..25694ecf5e
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_divider.py
@@ -0,0 +1,8 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axes_grid1.axes_divider import Divider, AxesLocator, SubplotDivider, \
+ AxesDivider, locatable_axes_factory, make_axes_locatable
+
+from mpl_toolkits.axes_grid.axislines import Axes
+LocatableAxes = locatable_axes_factory(Axes)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_grid.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_grid.py
new file mode 100644
index 0000000000..58212ac89c
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_grid.py
@@ -0,0 +1,30 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import mpl_toolkits.axes_grid1.axes_grid as axes_grid_orig
+from .axes_divider import LocatableAxes
+
+class CbarAxes(axes_grid_orig.CbarAxesBase, LocatableAxes):
+ def __init__(self, *kl, **kwargs):
+ orientation=kwargs.pop("orientation", None)
+ if orientation is None:
+ raise ValueError("orientation must be specified")
+ self.orientation = orientation
+ self._default_label_on = False
+ self.locator = None
+
+ super(LocatableAxes, self).__init__(*kl, **kwargs)
+
+ def cla(self):
+ super(LocatableAxes, self).cla()
+ self._config_axes()
+
+
+class Grid(axes_grid_orig.Grid):
+ _defaultLocatableAxesClass = LocatableAxes
+
+class ImageGrid(axes_grid_orig.ImageGrid):
+ _defaultLocatableAxesClass = LocatableAxes
+ _defaultCbarAxesClass = CbarAxes
+
+AxesGrid = ImageGrid
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_rgb.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_rgb.py
new file mode 100644
index 0000000000..bfd4bb98ad
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_rgb.py
@@ -0,0 +1,11 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+#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 .axislines import Axes
+
+class RGBAxes(RGBAxesBase):
+ _defaultAxesClass = Axes
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_size.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_size.py
new file mode 100644
index 0000000000..998b5e3c87
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axes_size.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axes_grid1.axes_size import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axis_artist.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axis_artist.py
new file mode 100644
index 0000000000..92f0538ceb
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axis_artist.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axisartist.axis_artist import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axisline_style.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axisline_style.py
new file mode 100644
index 0000000000..2eef3b8b34
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axisline_style.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axisartist.axisline_style import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axislines.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axislines.py
new file mode 100644
index 0000000000..9653aa1702
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/axislines.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axisartist.axislines import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/clip_path.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/clip_path.py
new file mode 100644
index 0000000000..bafe568fb1
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/clip_path.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axisartist.clip_path import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/colorbar.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/colorbar.py
new file mode 100644
index 0000000000..cc5c252da8
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/colorbar.py
@@ -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
+)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/floating_axes.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/floating_axes.py
new file mode 100644
index 0000000000..3f30d57c3a
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/floating_axes.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axisartist.floating_axes import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/grid_finder.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/grid_finder.py
new file mode 100644
index 0000000000..ffa3db76cf
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/grid_finder.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axisartist.grid_finder import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/grid_helper_curvelinear.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/grid_helper_curvelinear.py
new file mode 100644
index 0000000000..325ddd6af2
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/grid_helper_curvelinear.py
@@ -0,0 +1,4 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axisartist.grid_helper_curvelinear import *
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/inset_locator.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/inset_locator.py
new file mode 100644
index 0000000000..a9ed77beda
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/inset_locator.py
@@ -0,0 +1,7 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axes_grid1.inset_locator import InsetPosition, \
+ AnchoredSizeLocator, \
+ AnchoredZoomLocator, BboxPatch, BboxConnector, BboxConnectorPatch, \
+ inset_axes, zoomed_inset_axes, mark_inset
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/parasite_axes.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/parasite_axes.py
new file mode 100644
index 0000000000..cad56e43a2
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid/parasite_axes.py
@@ -0,0 +1,18 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+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)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/__init__.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/__init__.py
new file mode 100644
index 0000000000..3e225ba9f0
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/__init__.py
@@ -0,0 +1,12 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/anchored_artists.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/anchored_artists.py
new file mode 100644
index 0000000000..5b492858e8
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/anchored_artists.py
@@ -0,0 +1,376 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+import six
+
+from matplotlib import docstring
+from matplotlib.offsetbox import (AnchoredOffsetbox, AuxTransformBox,
+ DrawingArea, TextArea, VPacker)
+from matplotlib.patches import Rectangle, Ellipse
+
+
+__all__ = ['AnchoredDrawingArea', 'AnchoredAuxTransformBox',
+ 'AnchoredEllipse', 'AnchoredSizeBar']
+
+
+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=1, 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(AnchoredDrawingArea, self).__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=2)
+ >>> 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)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_divider.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_divider.py
new file mode 100644
index 0000000000..b238e73cc5
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_divider.py
@@ -0,0 +1,975 @@
+"""
+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.
+
+"""
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+from six.moves import map
+
+import matplotlib.transforms as mtransforms
+
+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, **kwargs):
+ """
+ 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
+
+ horizontal = kwargs.pop("horizontal", [])
+ vertical = kwargs.pop("vertical", [])
+ aspect = kwargs.pop("aspect", None)
+ anchor = kwargs.pop("anchor", "C")
+
+ if kwargs:
+ raise Exception("")
+
+ Divider.__init__(self, fig, pos, horizontal, vertical,
+ 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, **kwargs):
+ axes = self._axes
+
+ axes_class = kwargs.pop("axes_class", None)
+
+ if axes_class is None:
+ if isinstance(axes, SubplotBase):
+ axes_class = axes._axes_class
+ else:
+ axes_class = type(axes)
+
+ ax = axes_class(axes.get_figure(),
+ axes.get_position(original=True), **kwargs)
+
+ return ax
+
+ 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)
+
+
+class LocatableAxesBase(object):
+ def __init__(self, *kl, **kw):
+
+ self._axes_class.__init__(self, *kl, **kw)
+
+ self._locator = None
+ self._locator_renderer = None
+
+ def set_axes_locator(self, locator):
+ self._locator = locator
+
+ def get_axes_locator(self):
+ return self._locator
+
+ def apply_aspect(self, position=None):
+
+ if self.get_axes_locator() is None:
+ self._axes_class.apply_aspect(self, position)
+ else:
+ pos = self.get_axes_locator()(self, self._locator_renderer)
+ self._axes_class.apply_aspect(self, position=pos)
+
+ def draw(self, renderer=None, inframe=False):
+
+ self._locator_renderer = renderer
+
+ self._axes_class.draw(self, renderer, inframe)
+
+ def _make_twin_axes(self, *kl, **kwargs):
+ """
+ Need to overload so that twinx/twiny will work with
+ these axes.
+ """
+ if 'sharex' in kwargs and 'sharey' in kwargs:
+ raise ValueError("Twinned Axes may share only one axis.")
+ ax2 = type(self)(self.figure, self.get_position(True), *kl, **kwargs)
+ ax2.set_axes_locator(self.get_axes_locator())
+ self.figure.add_axes(ax2)
+ self.set_adjustable('datalim')
+ ax2.set_adjustable('datalim')
+ self._twinned_axes.join(self, ax2)
+ return ax2
+
+_locatableaxes_classes = {}
+
+
+def locatable_axes_factory(axes_class):
+
+ new_class = _locatableaxes_classes.get(axes_class)
+ if new_class is None:
+ new_class = type(str("Locatable%s" % (axes_class.__name__)),
+ (LocatableAxesBase, axes_class),
+ {'_axes_class': axes_class})
+
+ _locatableaxes_classes[axes_class] = new_class
+
+ return new_class
+
+#if hasattr(maxes.Axes, "get_axes_locator"):
+# LocatableAxes = maxes.Axes
+#else:
+
+
+def make_axes_locatable(axes):
+ if not hasattr(axes, "set_axes_locator"):
+ new_class = locatable_axes_factory(type(axes))
+ axes.__class__ = new_class
+
+ 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 matplotlib.axes import Axes
+from .mpl_axes import Axes
+LocatableAxes = locatable_axes_factory(Axes)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_grid.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_grid.py
new file mode 100644
index 0000000000..dde0e8dd7c
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_grid.py
@@ -0,0 +1,771 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+import matplotlib.axes as maxes
+import matplotlib.cbook as cbook
+import matplotlib.ticker as ticker
+from matplotlib.gridspec import SubplotSpec
+
+from .axes_divider import Size, SubplotDivider, LocatableAxes, Divider
+from .colorbar import Colorbar
+
+
+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, **kwargs):
+ locator = kwargs.pop("locator", None)
+
+ 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
+
+ self._hold = True
+ 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, LocatableAxes):
+ def __init__(self, *kl, **kwargs):
+ orientation = kwargs.pop("orientation", None)
+ if orientation is None:
+ raise ValueError("orientation must be specified")
+ self.orientation = orientation
+ self._default_label_on = True
+ self.locator = None
+
+ super(LocatableAxes, self).__init__(*kl, **kwargs)
+
+ def cla(self):
+ super(LocatableAxes, self).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.
+ """
+
+ _defaultLocatableAxesClass = LocatableAxes
+
+ 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 (ngrids > self._nrows * self._ncols) or (ngrids <= 0):
+ 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._defaultLocatableAxesClass
+ axes_class_args = {}
+ else:
+ if (type(axes_class)) == type and \
+ issubclass(axes_class,
+ self._defaultLocatableAxesClass.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, six.string_types) or cbook.is_numlike(rect):
+ 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._defaultLocatableAxesClass
+ 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, six.string_types) or cbook.is_numlike(rect):
+ 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
+
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_rgb.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_rgb.py
new file mode 100644
index 0000000000..e62d4f0615
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_rgb.py
@@ -0,0 +1,228 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+import numpy as np
+from .axes_divider import make_axes_locatable, Size, locatable_axes_factory
+import sys
+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 = locatable_axes_factory(ax._axes_class)
+ except AttributeError:
+ axes_class = locatable_axes_factory(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, *kl, **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
+ """
+ pad = kwargs.pop("pad", 0.0)
+ add_all = kwargs.pop("add_all", True)
+ try:
+ axes_class = kwargs.pop("axes_class", self._defaultAxesClass)
+ except AttributeError:
+ new_msg = ("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.")
+ six.reraise(AttributeError, AttributeError(new_msg),
+ sys.exc_info()[2])
+
+ ax = axes_class(*kl, **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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_size.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_size.py
new file mode 100644
index 0000000000..163a6245fe
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/axes_size.py
@@ -0,0 +1,323 @@
+
+"""
+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 __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+import matplotlib.cbook as cbook
+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 cbook.is_numlike(size):
+ return Fixed(size)
+ elif isinstance(size, six.string_types):
+ 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(tight_bbox, axes_bbox):
+ return axes_bbox.xmin - tight_bbox.xmin
+
+ def _get_right(tight_bbox, axes_bbox):
+ return tight_bbox.xmax - axes_bbox.xmax
+
+ def _get_bottom(tight_bbox, axes_bbox):
+ return axes_bbox.ymin - tight_bbox.ymin
+
+ def _get_top(tight_bbox, axes_bbox):
+ return tight_bbox.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, False),
+ ax.bbox) for ax in self._ax_list]
+ return max(vl)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/colorbar.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/colorbar.py
new file mode 100644
index 0000000000..34bdf3618a
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/colorbar.py
@@ -0,0 +1,836 @@
+"""
+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`.
+"""
+
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+from six.moves import xrange, zip
+
+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 pylab
+ 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, six.string_types):
+ 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 xrange(1, N-1)]
+ else:
+ return [list(zip(Y[i], X[i])) for i in xrange(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.
+ '''
+ del self.lines
+
+ N = len(levels)
+ x = np.array([1.0, 2.0])
+ X, Y = np.meshgrid(x,levels)
+ if self.orientation == 'vertical':
+ xy = [list(zip(X[i], Y[i])) for i in xrange(N)]
+ else:
+ xy = [list(zip(Y[i], X[i])) for i in xrange(N)]
+ 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, **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')
+ fraction = kw.pop('fraction', 0.15)
+ shrink = kw.pop('shrink', 1.0)
+ aspect = kw.pop('aspect', 20)
+ #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 pylab 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)
+ cax._hold = True
+ 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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/inset_locator.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/inset_locator.py
new file mode 100644
index 0000000000..9aeedcb088
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/inset_locator.py
@@ -0,0 +1,659 @@
+"""
+A collection of functions and objects for creating or placing inset axes.
+"""
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import warnings
+from matplotlib import docstring
+import six
+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(AnchoredLocatorBase, self).__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(AnchoredSizeLocator, self).__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(AnchoredZoomLocator, self).__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_transfrom()
+ 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()
+ Patch.__init__(self, fill=False, **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 = (list(path1.vertices) +
+ list(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=1,
+ 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>`.
+
+ 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,
+ *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)
+
+ 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=1,
+ 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)
+
+ fill = kwargs.pop("fill", False)
+ 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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/mpl_axes.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/mpl_axes.py
new file mode 100644
index 0000000000..aaff7b7692
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/mpl_axes.py
@@ -0,0 +1,154 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+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(Axes.AxisDict, self).__init__()
+
+ def __getitem__(self, k):
+ if isinstance(k, tuple):
+ r = SimpleChainedObjects(
+ [super(Axes.AxisDict, self).__getitem__(k1) for k1 in k])
+ return r
+ elif isinstance(k, slice):
+ if k.start is None and k.stop is None and k.step is None:
+ r = SimpleChainedObjects(list(six.itervalues(self)))
+ return r
+ 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__(self, *kl, **kw):
+ super(Axes, self).__init__(*kl, **kw)
+
+ def _init_axis_artists(self, axes=None):
+ if axes is None:
+ axes = self
+
+ self._axislines = self.AxisDict(self)
+
+ self._axislines["bottom"] = SimpleAxisArtist(self.xaxis, 1, self.spines["bottom"])
+ self._axislines["top"] = SimpleAxisArtist(self.xaxis, 2, self.spines["top"])
+ self._axislines["left"] = SimpleAxisArtist(self.yaxis, 1, self.spines["left"])
+ self._axislines["right"] = SimpleAxisArtist(self.yaxis, 2, self.spines["right"])
+
+
+ def _get_axislines(self):
+ return self._axislines
+
+ axis = property(_get_axislines)
+
+ def cla(self):
+
+ super(Axes, self).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()
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/parasite_axes.py b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/parasite_axes.py
new file mode 100644
index 0000000000..16a67b4d1f
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axes_grid1/parasite_axes.py
@@ -0,0 +1,486 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+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(object):
+
+ 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, **kargs):
+
+ self._parent_axes = parent_axes
+ kargs.update(dict(frameon=False))
+ self._get_base_axes_attr("__init__")(self, parent_axes.figure,
+ parent_axes._position, **kargs)
+
+ def cla(self):
+ self._get_base_axes_attr("cla")(self)
+
+ 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)
+
+
+_parasite_axes_classes = {}
+def parasite_axes_class_factory(axes_class=None):
+ if axes_class is None:
+ axes_class = Axes
+
+ new_class = _parasite_axes_classes.get(axes_class)
+ if new_class is None:
+ def _get_base_axes_attr(self, attrname):
+ return getattr(axes_class, attrname)
+
+ new_class = type(str("%sParasite" % (axes_class.__name__)),
+ (ParasiteAxesBase, axes_class),
+ {'_get_base_axes_attr': _get_base_axes_attr})
+ _parasite_axes_classes[axes_class] = new_class
+
+ return new_class
+
+ParasiteAxes = parasite_axes_class_factory()
+
+# #class ParasiteAxes(ParasiteAxesBase, Axes):
+
+# @classmethod
+# def _get_base_axes_attr(cls, attrname):
+# return getattr(Axes, attrname)
+
+
+
+class ParasiteAxesAuxTransBase(object):
+ def __init__(self, parent_axes, aux_transform, viewlim_mode=None,
+ **kwargs):
+
+ self.transAux = aux_transform
+ self.set_viewlim_mode(viewlim_mode)
+
+ self._parasite_axes_class.__init__(self, 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, method_name, *XYC, **kwargs):
+ if len(XYC) == 1:
+ C = XYC[0]
+ ny, nx = C.shape
+
+ gx = np.arange(-0.5, nx, 1.)
+ gy = np.arange(-0.5, ny, 1.)
+
+ X, Y = np.meshgrid(gx, gy)
+ else:
+ X, Y, C = XYC
+
+ pcolor_routine = self._get_base_axes_attr(method_name)
+
+ if "transform" in kwargs:
+ mesh = pcolor_routine(self, X, Y, C, **kwargs)
+ else:
+ orig_shape = X.shape
+ xy = np.vstack([X.flat, Y.flat])
+ xyt=xy.transpose()
+ wxy = self.transAux.transform(xyt)
+ gx, gy = wxy[:,0].reshape(orig_shape), wxy[:,1].reshape(orig_shape)
+ mesh = pcolor_routine(self, gx, gy, C, **kwargs)
+ mesh.set_transform(self._parent_axes.transData)
+
+ return mesh
+
+ def pcolormesh(self, *XYC, **kwargs):
+ return self._pcolor("pcolormesh", *XYC, **kwargs)
+
+ def pcolor(self, *XYC, **kwargs):
+ return self._pcolor("pcolor", *XYC, **kwargs)
+
+
+ def _contour(self, method_name, *XYCL, **kwargs):
+
+ if len(XYCL) <= 2:
+ C = XYCL[0]
+ ny, nx = C.shape
+
+ gx = np.arange(0., nx, 1.)
+ gy = np.arange(0., ny, 1.)
+
+ X,Y = np.meshgrid(gx, gy)
+ CL = XYCL
+ else:
+ X, Y = XYCL[:2]
+ CL = XYCL[2:]
+
+ contour_routine = self._get_base_axes_attr(method_name)
+
+ if "transform" in kwargs:
+ cont = contour_routine(self, X, Y, *CL, **kwargs)
+ else:
+ orig_shape = X.shape
+ xy = np.vstack([X.flat, Y.flat])
+ xyt=xy.transpose()
+ wxy = self.transAux.transform(xyt)
+ gx, gy = wxy[:,0].reshape(orig_shape), wxy[:,1].reshape(orig_shape)
+ cont = contour_routine(self, 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("contour", *XYCL, **kwargs)
+
+ def contourf(self, *XYCL, **kwargs):
+ return self._contour("contourf", *XYCL, **kwargs)
+
+ def apply_aspect(self, position=None):
+ self.update_viewlim()
+ self._get_base_axes_attr("apply_aspect")(self)
+ #ParasiteAxes.apply_aspect()
+
+
+
+_parasite_axes_auxtrans_classes = {}
+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
+
+ new_class = _parasite_axes_auxtrans_classes.get(parasite_axes_class)
+ if new_class is None:
+ new_class = type(str("%sParasiteAuxTrans" % (parasite_axes_class.__name__)),
+ (ParasiteAxesAuxTransBase, parasite_axes_class),
+ {'_parasite_axes_class': parasite_axes_class,
+ 'name': 'parasite_axes'})
+ _parasite_axes_auxtrans_classes[parasite_axes_class] = new_class
+
+ return new_class
+
+
+ParasiteAxesAuxTrans = parasite_axes_auxtrans_class_factory(axes_class=ParasiteAxes)
+
+
+
+
+def _get_handles(ax):
+ handles = ax.lines[:]
+ handles.extend(ax.patches)
+ handles.extend([c for c in ax.collections
+ if isinstance(c, mcoll.LineCollection)])
+ handles.extend([c for c in ax.collections
+ if isinstance(c, mcoll.RegularPolyCollection)])
+ handles.extend([c for c in ax.collections
+ if isinstance(c, mcoll.CircleCollection)])
+
+ return handles
+
+
+class HostAxesBase(object):
+ def __init__(self, *args, **kwargs):
+
+ self.parasites = []
+ self._get_base_axes_attr("__init__")(self, *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 = lambda h: self.parasites.remove(h)
+ return ax2
+
+ def _get_legend_handles(self, legend_handler_map=None):
+ # don't use this!
+ Axes_get_legend_handles = self._get_base_axes_attr("_get_legend_handles")
+ all_handles = list(Axes_get_legend_handles(self, legend_handler_map))
+
+ 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)
+
+ self._get_base_axes_attr("draw")(self, renderer)
+ self.artists = orig_artists
+ self.images = orig_images
+
+
+ def cla(self):
+
+ for ax in self.parasites:
+ ax.cla()
+
+ self._get_base_axes_attr("cla")(self)
+ #super(HostAxes, self).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)
+
+ self.axis["right"].set_visible(False)
+
+ ax2.axis["right"].set_visible(True)
+ ax2.axis["left", "top", "bottom"].set_visible(False)
+
+ def _remove_method(h):
+ self.parasites.remove(h)
+ self.axis["right"].set_visible(True)
+ self.axis["right"].toggle(ticklabels=False, label=False)
+ ax2._remove_method = _remove_method
+
+ return ax2
+
+ 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)
+
+ self.axis["top"].set_visible(False)
+
+ ax2.axis["top"].set_visible(True)
+ ax2.axis["left", "right", "bottom"].set_visible(False)
+
+ def _remove_method(h):
+ self.parasites.remove(h)
+ self.axis["top"].set_visible(True)
+ self.axis["top"].toggle(ticklabels=False, label=False)
+ ax2._remove_method = _remove_method
+
+ return ax2
+
+
+ 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 = lambda h: self.parasites.remove(h)
+
+ 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):
+
+ bbs = [ax.get_tightbbox(renderer, call_axes_locator)
+ for ax in self.parasites]
+ get_tightbbox = self._get_base_axes_attr("get_tightbbox")
+ bbs.append(get_tightbbox(self, renderer, call_axes_locator))
+
+ _bbox = Bbox.union([b for b in bbs if b.width!=0 or b.height!=0])
+
+ return _bbox
+
+
+
+_host_axes_classes = {}
+def host_axes_class_factory(axes_class=None):
+ if axes_class is None:
+ axes_class = Axes
+
+ new_class = _host_axes_classes.get(axes_class)
+ if new_class is None:
+ def _get_base_axes(self):
+ return axes_class
+
+ def _get_base_axes_attr(self, attrname):
+ return getattr(axes_class, attrname)
+
+ new_class = type(str("%sHostAxes" % (axes_class.__name__)),
+ (HostAxesBase, axes_class),
+ {'_get_base_axes_attr': _get_base_axes_attr,
+ '_get_base_axes': _get_base_axes})
+
+ _host_axes_classes[axes_class] = new_class
+
+ return new_class
+
+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, **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
+ axes_class = kwargs.pop("axes_class", None)
+ host_axes_class = host_axes_class_factory(axes_class)
+ fig = kwargs.get("figure", None)
+ if fig is None:
+ fig = plt.gcf()
+ ax = host_axes_class(fig, *args, **kwargs)
+ fig.add_axes(ax)
+ plt.draw_if_interactive()
+ return ax
+
+def host_subplot(*args, **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
+ axes_class = kwargs.pop("axes_class", None)
+ host_subplot_class = host_subplot_class_factory(axes_class)
+ fig = kwargs.get("figure", None)
+ if fig is None:
+ fig = plt.gcf()
+ ax = host_subplot_class(fig, *args, **kwargs)
+ fig.add_subplot(ax)
+ plt.draw_if_interactive()
+ return ax
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/__init__.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/__init__.py
new file mode 100644
index 0000000000..8431c0cd3e
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/__init__.py
@@ -0,0 +1,26 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+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)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/angle_helper.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/angle_helper.py
new file mode 100644
index 0000000000..15732a58ec
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/angle_helper.py
@@ -0,0 +1,416 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+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.transfrom_xy = transform_xy
+ #self.inv_transfrom_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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_divider.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_divider.py
new file mode 100644
index 0000000000..5294940530
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_divider.py
@@ -0,0 +1,9 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axes_grid1.axes_divider import (
+ Divider, AxesLocator, SubplotDivider, AxesDivider, locatable_axes_factory,
+ make_axes_locatable)
+
+from mpl_toolkits.axes_grid.axislines import Axes
+LocatableAxes = locatable_axes_factory(Axes)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_grid.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_grid.py
new file mode 100644
index 0000000000..58212ac89c
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_grid.py
@@ -0,0 +1,30 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import mpl_toolkits.axes_grid1.axes_grid as axes_grid_orig
+from .axes_divider import LocatableAxes
+
+class CbarAxes(axes_grid_orig.CbarAxesBase, LocatableAxes):
+ def __init__(self, *kl, **kwargs):
+ orientation=kwargs.pop("orientation", None)
+ if orientation is None:
+ raise ValueError("orientation must be specified")
+ self.orientation = orientation
+ self._default_label_on = False
+ self.locator = None
+
+ super(LocatableAxes, self).__init__(*kl, **kwargs)
+
+ def cla(self):
+ super(LocatableAxes, self).cla()
+ self._config_axes()
+
+
+class Grid(axes_grid_orig.Grid):
+ _defaultLocatableAxesClass = LocatableAxes
+
+class ImageGrid(axes_grid_orig.ImageGrid):
+ _defaultLocatableAxesClass = LocatableAxes
+ _defaultCbarAxesClass = CbarAxes
+
+AxesGrid = ImageGrid
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_rgb.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_rgb.py
new file mode 100644
index 0000000000..695a362b57
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axes_rgb.py
@@ -0,0 +1,11 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+from mpl_toolkits.axes_grid1.axes_rgb import (
+ make_rgb_axes, imshow_rgb, RGBAxesBase)
+
+from .axislines import Axes
+
+
+class RGBAxes(RGBAxesBase):
+ _defaultAxesClass = Axes
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axis_artist.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axis_artist.py
new file mode 100644
index 0000000000..620232112c
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axis_artist.py
@@ -0,0 +1,1527 @@
+"""
+axis_artist.py module provides axis-related artists. They are
+
+ * axis line
+ * tick lines
+ * tick labels
+ * axis label
+ * grid lines
+
+The main artist class is a AxisArtist and a GridlinesCollection. The
+GridlinesCollection is responsible for drawing grid lines and the
+AxisArtist is responsible for all other artists. The AxisArtist class
+has attributes that are associated with each type of artists.
+
+ * line : axis line
+ * major_ticks : major tick lines
+ * major_ticklabels : major tick labels
+ * minor_ticks : minor tick lines
+ * minor_ticklabels : minor tick labels
+ * label : axis label
+
+Typically, the AxisArtist associated with a axes will be accessed with
+the *axis* dictionary of the axes, i.e., the AxisArtist for the bottom
+axis is
+
+ ax.axis["bottom"]
+
+where *ax* is an instance of axes (mpl_toolkits.axislines.Axes). Thus,
+ax.axis["bottom"].line is an artist associated with the axis line, and
+ax.axis["bottom"].major_ticks is an artist associated with the major tick
+lines.
+
+You can change the colors, fonts, line widths, etc. of these artists
+by calling suitable set method. For example, to change the color of the major
+ticks of the bottom axis to red,
+
+ ax.axis["bottom"].major_ticks.set_color("r")
+
+However, things like the locations of ticks, and their ticklabels need
+to be changed from the side of the grid_helper.
+
+axis_direction
+--------------
+
+AxisArtist, AxisLabel, TickLabels have *axis_direction* attribute,
+which adjusts the location, angle, etc.,. The *axis_direction* must be
+one of [left, right, bottom, top] and they follow the matplotlib
+convention for the rectangle axis.
+
+For example, for the *bottom* axis (the left and right is relative to
+the direction of the increasing coordinate),
+
+ * ticklabels and axislabel are on the right
+ * ticklabels and axislabel have text angle of 0
+ * ticklabels are baseline, center-aligned
+ * axislabel is top, center-aligned
+
+
+The text angles are actually relative to (90 + angle of the direction
+to the ticklabel), which gives 0 for bottom axis.
+
+ Parameter left bottom right top
+ ticklabels location left right right left
+ axislabel location left right right left
+ ticklabels angle 90 0 -90 180
+ axislabel angle 180 0 0 180
+ ticklabel va center baseline center baseline
+ axislabel va center top center bottom
+ ticklabel ha right center right center
+ axislabel ha right center right center
+
+
+Ticks are by default direct opposite side of the ticklabels. To make
+ticks to the same side of the ticklabels,
+
+ ax.axis["bottom"].major_ticks.set_ticks_out(True)
+
+
+Following attributes can be customized (use set_xxx method)
+
+ * Ticks : ticksize, tick_out
+ * TickLabels : pad
+ * AxisLabel : pad
+
+"""
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+# FIXME :
+# angles are given in data coordinate - need to convert it to canvas coordinate
+
+
+import matplotlib.artist as martist
+import matplotlib.text as mtext
+import matplotlib.font_manager as font_manager
+
+from matplotlib.path import Path
+from matplotlib.transforms import (
+ Affine2D, Bbox, IdentityTransform, ScaledTranslation, TransformedPath)
+from matplotlib.collections import LineCollection
+
+from matplotlib import rcParams
+
+from matplotlib.artist import allow_rasterization
+
+import warnings
+
+import numpy as np
+
+
+import matplotlib.lines as mlines
+from .axisline_style import AxislineStyle
+
+
+class BezierPath(mlines.Line2D):
+
+ def __init__(self, path, *kl, **kw):
+ mlines.Line2D.__init__(self, [], [], *kl, **kw)
+ self._path = path
+ self._invalid = False
+
+ def recache(self):
+
+ self._transformed_path = TransformedPath(self._path, self.get_transform())
+
+ self._invalid = False
+
+ def set_path(self, path):
+ self._path = path
+ self._invalid = True
+
+
+ def draw(self, renderer):
+ if self._invalid:
+ self.recache()
+
+ if not self._visible: return
+ renderer.open_group('line2d')
+
+ gc = renderer.new_gc()
+ self._set_gc_clip(gc)
+
+ gc.set_foreground(self._color)
+ gc.set_antialiased(self._antialiased)
+ gc.set_linewidth(self._linewidth)
+ gc.set_alpha(self._alpha)
+ if self.is_dashed():
+ cap = self._dashcapstyle
+ join = self._dashjoinstyle
+ else:
+ cap = self._solidcapstyle
+ join = self._solidjoinstyle
+ gc.set_joinstyle(join)
+ gc.set_capstyle(cap)
+ gc.set_dashes(self._dashOffset, self._dashSeq)
+
+ if self._lineStyles[self._linestyle] != '_draw_nothing':
+ tpath, affine = (
+ self._transformed_path.get_transformed_path_and_affine())
+ renderer.draw_path(gc, tpath, affine.frozen())
+
+ gc.restore()
+ renderer.close_group('line2d')
+
+
+
+class UnimplementedException(Exception):
+ pass
+
+from matplotlib.artist import Artist
+
+class AttributeCopier(object):
+ def __init__(self, ref_artist, klass=Artist):
+ self._klass = klass
+ self._ref_artist = ref_artist
+ super(AttributeCopier, self).__init__()
+
+ def set_ref_artist(self, artist):
+ self._ref_artist = artist
+
+ def get_ref_artist(self):
+ raise RuntimeError("get_ref_artist must overridden")
+ #return self._ref_artist
+
+ def get_attribute_from_ref_artist(self, attr_name, default_value):
+ get_attr_method_name = "get_"+attr_name
+ c = getattr(self._klass, get_attr_method_name)(self)
+ if c == 'auto':
+ ref_artist = self.get_ref_artist()
+ if ref_artist:
+ attr = getattr(ref_artist,
+ get_attr_method_name)()
+ return attr
+ else:
+ return default_value
+
+ return c
+
+
+from matplotlib.lines import Line2D
+
+class Ticks(Line2D, AttributeCopier):
+ """
+ Ticks are derived from Line2D, and note that ticks themselves
+ are markers. Thus, you should use set_mec, set_mew, etc.
+
+ To change the tick size (length), you need to use
+ set_ticksize. To change the direction of the ticks (ticks are
+ in opposite direction of ticklabels by default), use
+ set_tick_out(False).
+ """
+
+ def __init__(self, ticksize, tick_out=False, **kwargs):
+ self._ticksize = ticksize
+ self.locs_angles_labels = []
+
+ self.set_tick_out(tick_out)
+
+ self._axis = kwargs.pop("axis", None)
+ if self._axis is not None:
+ if "color" not in kwargs:
+ kwargs["color"] = "auto"
+ if ("mew" not in kwargs) and ("markeredgewidth" not in kwargs):
+ kwargs["markeredgewidth"] = "auto"
+
+ Line2D.__init__(self, [0.], [0.], **kwargs)
+ AttributeCopier.__init__(self, self._axis, klass=Line2D)
+ self.set_snap(True)
+
+ def get_ref_artist(self):
+ #return self._ref_artist.get_ticklines()[0]
+ return self._ref_artist.majorTicks[0].tick1line
+
+ def get_color(self):
+ return self.get_attribute_from_ref_artist("color", "k")
+
+ def get_markeredgecolor(self):
+ if self._markeredgecolor == 'auto':
+ return self.get_color()
+ else:
+ return self._markeredgecolor
+
+ def get_markeredgewidth(self):
+ return self.get_attribute_from_ref_artist("markeredgewidth", .5)
+
+
+ def set_tick_out(self, b):
+ """
+ set True if tick need to be rotated by 180 degree.
+ """
+ self._tick_out = b
+
+ def get_tick_out(self):
+ """
+ Return True if the tick will be rotated by 180 degree.
+ """
+ return self._tick_out
+
+
+ def set_ticksize(self, ticksize):
+ """
+ set length of the ticks in points.
+ """
+ self._ticksize = ticksize
+
+
+ def get_ticksize(self):
+ """
+ Return length of the ticks in points.
+ """
+ return self._ticksize
+
+ def set_locs_angles(self, locs_angles):
+ self.locs_angles = locs_angles
+
+
+ def _update(self, renderer):
+ pass
+
+ _tickvert_path = Path([[0., 0.], [1., 0.]])
+
+ def draw(self, renderer):
+ if not self.get_visible():
+ return
+
+ self._update(renderer) # update the tick
+
+ size = self._ticksize
+ path_trans = self.get_transform()
+
+ # set gc : copied from lines.py
+# gc = renderer.new_gc()
+# self._set_gc_clip(gc)
+
+# gc.set_foreground(self.get_color())
+# gc.set_antialiased(self._antialiased)
+# gc.set_linewidth(self._linewidth)
+# gc.set_alpha(self._alpha)
+# if self.is_dashed():
+# cap = self._dashcapstyle
+# join = self._dashjoinstyle
+# else:
+# cap = self._solidcapstyle
+# join = self._solidjoinstyle
+# gc.set_joinstyle(join)
+# gc.set_capstyle(cap)
+# gc.set_snap(self.get_snap())
+
+
+ gc = renderer.new_gc()
+ gc.set_foreground(self.get_markeredgecolor())
+ gc.set_linewidth(self.get_markeredgewidth())
+ gc.set_alpha(self._alpha)
+
+ offset = renderer.points_to_pixels(size)
+ marker_scale = Affine2D().scale(offset, offset)
+
+ if self.get_tick_out():
+ add_angle = 180
+ else:
+ add_angle = 0
+
+ marker_rotation = Affine2D()
+ marker_transform = marker_scale + marker_rotation
+
+ for loc, angle in self.locs_angles:
+ marker_rotation.clear().rotate_deg(angle+add_angle)
+ locs = path_trans.transform_non_affine(np.array([loc]))
+ if self.axes and not self.axes.viewLim.contains(*locs[0]):
+ continue
+ renderer.draw_markers(gc, self._tickvert_path, marker_transform,
+ Path(locs), path_trans.get_affine())
+
+ gc.restore()
+
+
+class LabelBase(mtext.Text):
+ """
+ A base class for AxisLabel and TickLabels. The position and angle
+ of the text are calculated by to offset_ref_angle,
+ text_ref_angle, and offset_radius attributes.
+ """
+
+ def __init__(self, *kl, **kwargs):
+ self.locs_angles_labels = []
+ self._ref_angle = 0
+ self._offset_radius = 0.
+
+ super(LabelBase, self).__init__(*kl,
+ **kwargs)
+
+ self.set_rotation_mode("anchor")
+ self._text_follow_ref_angle = True
+ #self._offset_ref_angle = 0
+
+ def _set_ref_angle(self, a):
+ self._ref_angle = a
+
+ def _get_ref_angle(self):
+ return self._ref_angle
+
+ def _get_text_ref_angle(self):
+ if self._text_follow_ref_angle:
+ return self._get_ref_angle()+90
+ else:
+ return 0 #self.get_ref_angle()
+
+ def _get_offset_ref_angle(self):
+ return self._get_ref_angle()
+
+ def _set_offset_radius(self, offset_radius):
+ self._offset_radius = offset_radius
+
+ def _get_offset_radius(self):
+ return self._offset_radius
+
+
+ _get_opposite_direction = {"left":"right",
+ "right":"left",
+ "top":"bottom",
+ "bottom":"top"}.__getitem__
+
+
+ def _update(self, renderer):
+ pass
+
+ def draw(self, renderer):
+ if not self.get_visible(): return
+
+ self._update(renderer)
+
+ # save original and adjust some properties
+ tr = self.get_transform()
+ angle_orig = self.get_rotation()
+
+ offset_tr = Affine2D()
+ self.set_transform(tr+offset_tr)
+
+ text_ref_angle = self._get_text_ref_angle()
+ offset_ref_angle = self._get_offset_ref_angle()
+
+ theta = (offset_ref_angle)/180.*np.pi
+ dd = self._get_offset_radius()
+ dx, dy = dd * np.cos(theta), dd * np.sin(theta)
+ offset_tr.translate(dx, dy)
+ self.set_rotation(text_ref_angle+angle_orig)
+ super(LabelBase, self).draw(renderer)
+ offset_tr.clear()
+
+
+ # restore original properties
+ self.set_transform(tr)
+ self.set_rotation(angle_orig)
+
+
+ def get_window_extent(self, renderer):
+
+ self._update(renderer)
+
+ # save original and adjust some properties
+ tr = self.get_transform()
+ angle_orig = self.get_rotation()
+
+ offset_tr = Affine2D()
+ self.set_transform(tr+offset_tr)
+
+ text_ref_angle = self._get_text_ref_angle()
+ offset_ref_angle = self._get_offset_ref_angle()
+
+ theta = (offset_ref_angle)/180.*np.pi
+ dd = self._get_offset_radius()
+ dx, dy = dd * np.cos(theta), dd * np.sin(theta)
+ offset_tr.translate(dx, dy)
+ self.set_rotation(text_ref_angle+angle_orig)
+
+ bbox = super(LabelBase, self).get_window_extent(renderer).frozen()
+
+ offset_tr.clear()
+
+
+ # restore original properties
+ self.set_transform(tr)
+ self.set_rotation(angle_orig)
+
+ return bbox
+
+
+class AxisLabel(LabelBase, AttributeCopier):
+ """
+ Axis Label. Derived from Text. The position of the text is updated
+ in the fly, so changing text position has no effect. Otherwise, the
+ properties can be changed as a normal Text.
+
+ To change the pad between ticklabels and axis label, use set_pad.
+ """
+
+ def __init__(self, *kl, **kwargs):
+
+ axis_direction = kwargs.pop("axis_direction", "bottom")
+ self._axis = kwargs.pop("axis", None)
+ #super(AxisLabel, self).__init__(*kl, **kwargs)
+ LabelBase.__init__(self, *kl, **kwargs)
+ AttributeCopier.__init__(self, self._axis, klass=LabelBase)
+
+ self.set_axis_direction(axis_direction)
+ self._pad = 5
+ self._extra_pad = 0
+
+ def set_pad(self, pad):
+ """
+ Set the pad in points. Note that the actual pad will be the
+ sum of the internal pad and the external pad (that are set
+ automatically by the AxisArtist), and it only set the internal
+ pad
+ """
+ self._pad = pad
+
+ def get_pad(self):
+ """
+ return pad in points. See set_pad for more details.
+ """
+ return self._pad
+
+
+ def _set_external_pad(self, p):
+ """
+ Set external pad IN PIXELS. This is intended to be set by the
+ AxisArtist, bot by user..
+ """
+ self._extra_pad = p
+
+ def _get_external_pad(self):
+ """
+ Get external pad.
+ """
+ return self._extra_pad
+
+
+ def get_ref_artist(self):
+ return self._axis.get_label()
+
+
+ def get_text(self):
+ t = super(AxisLabel, self).get_text()
+ if t == "__from_axes__":
+ return self._axis.get_label().get_text()
+ return self._text
+
+ _default_alignments = dict(left=("bottom", "center"),
+ right=("top", "center"),
+ bottom=("top", "center"),
+ top=("bottom", "center"))
+
+
+
+ def set_default_alignment(self, d):
+ if d not in ["left", "right", "top", "bottom"]:
+ raise ValueError('direction must be on of "left", "right", "top", "bottom"')
+
+ va, ha = self._default_alignments[d]
+ self.set_va(va)
+ self.set_ha(ha)
+
+
+ _default_angles = dict(left=180,
+ right=0,
+ bottom=0,
+ top=180)
+
+
+ def set_default_angle(self, d):
+ if d not in ["left", "right", "top", "bottom"]:
+ raise ValueError('direction must be on of "left", "right", "top", "bottom"')
+
+ self.set_rotation(self._default_angles[d])
+
+
+ def set_axis_direction(self, d):
+ """
+ Adjust the text angle and text alignment of axis label
+ according to the matplotlib convention.
+
+
+ ===================== ========== ========= ========== ==========
+ property left bottom right top
+ ===================== ========== ========= ========== ==========
+ axislabel angle 180 0 0 180
+ axislabel va center top center bottom
+ axislabel ha right center right center
+ ===================== ========== ========= ========== ==========
+
+ Note that the text angles are actually relative to (90 + angle
+ of the direction to the ticklabel), which gives 0 for bottom
+ axis.
+
+ """
+ if d not in ["left", "right", "top", "bottom"]:
+ raise ValueError('direction must be on of "left", "right", "top", "bottom"')
+
+ self.set_default_alignment(d)
+ self.set_default_angle(d)
+
+ def get_color(self):
+ return self.get_attribute_from_ref_artist("color", "k")
+
+ def draw(self, renderer):
+ if not self.get_visible():
+ return
+
+ pad = renderer.points_to_pixels(self.get_pad())
+ r = self._get_external_pad() + pad
+ self._set_offset_radius(r)
+
+ super(AxisLabel, self).draw(renderer)
+
+
+ def get_window_extent(self, renderer):
+
+ if not self.get_visible():
+ return
+
+ pad = renderer.points_to_pixels(self.get_pad())
+ r = self._get_external_pad() + pad
+ self._set_offset_radius(r)
+
+ bb = super(AxisLabel, self).get_window_extent(renderer)
+
+ return bb
+
+
+class TickLabels(AxisLabel, AttributeCopier): # mtext.Text
+ """
+ Tick Labels. While derived from Text, this single artist draws all
+ ticklabels. As in AxisLabel, the position of the text is updated
+ in the fly, so changing text position has no effect. Otherwise,
+ the properties can be changed as a normal Text. Unlike the
+ ticklabels of the mainline matplotlib, properties of single
+ ticklabel alone cannot modified.
+
+ To change the pad between ticks and ticklabels, use set_pad.
+ """
+
+ def __init__(self, **kwargs):
+
+ axis_direction = kwargs.pop("axis_direction", "bottom")
+ AxisLabel.__init__(self, **kwargs)
+ self.set_axis_direction(axis_direction)
+ #self._axis_direction = axis_direction
+ self._axislabel_pad = 0
+ #self._extra_pad = 0
+
+
+ # attribute copier
+ def get_ref_artist(self):
+ return self._axis.get_ticklabels()[0]
+
+ def set_axis_direction(self, label_direction):
+ """
+ Adjust the text angle and text alignment of ticklabels
+ according to the matplotlib convention.
+
+ The *label_direction* must be one of [left, right, bottom,
+ top].
+
+ ===================== ========== ========= ========== ==========
+ property left bottom right top
+ ===================== ========== ========= ========== ==========
+ ticklabels angle 90 0 -90 180
+ ticklabel va center baseline center baseline
+ ticklabel ha right center right center
+ ===================== ========== ========= ========== ==========
+
+
+ Note that the text angles are actually relative to (90 + angle
+ of the direction to the ticklabel), which gives 0 for bottom
+ axis.
+
+ """
+
+ if label_direction not in ["left", "right", "top", "bottom"]:
+ raise ValueError('direction must be one of "left", "right", "top", "bottom"')
+
+ self._axis_direction = label_direction
+ self.set_default_alignment(label_direction)
+ self.set_default_angle(label_direction)
+
+
+ def invert_axis_direction(self):
+ label_direction = self._get_opposite_direction(self._axis_direction)
+ self.set_axis_direction(label_direction)
+
+ def _get_ticklabels_offsets(self, renderer, label_direction):
+ """
+ Calculates the offsets of the ticklabels from the tick and
+ their total heights. The offset only takes account the offset
+ due to the vertical alignment of the ticklabels, i.e.,if axis
+ direction is bottom and va is ;top', it will return 0. if va
+ is 'baseline', it will return (height-descent).
+ """
+ whd_list = self.get_texts_widths_heights_descents(renderer)
+
+ if not whd_list:
+ return 0, 0
+
+ r = 0
+ va, ha = self.get_va(), self.get_ha()
+
+ if label_direction == "left":
+ pad = max(w for w, h, d in whd_list)
+ if ha == "left":
+ r = pad
+ elif ha == "center":
+ r = .5 * pad
+ elif label_direction == "right":
+ pad = max(w for w, h, d in whd_list)
+ if ha == "right":
+ r = pad
+ elif ha == "center":
+ r = .5 * pad
+ elif label_direction == "bottom":
+ pad = max(h for w, h, d in whd_list)
+ if va == "bottom":
+ r = pad
+ elif va == "center":
+ r =.5 * pad
+ elif va == "baseline":
+ max_ascent = max(h - d for w, h, d in whd_list)
+ max_descent = max(d for w, h, d in whd_list)
+ r = max_ascent
+ pad = max_ascent + max_descent
+ elif label_direction == "top":
+ pad = max(h for w, h, d in whd_list)
+ if va == "top":
+ r = pad
+ elif va == "center":
+ r =.5 * pad
+ elif va == "baseline":
+ max_ascent = max(h - d for w, h, d in whd_list)
+ max_descent = max(d for w, h, d in whd_list)
+ r = max_descent
+ pad = max_ascent + max_descent
+
+ #tick_pad = renderer.points_to_pixels(self.get_pad())
+
+ # r : offset
+
+ # pad : total height of the ticklabels. This will be used to
+ # calculate the pad for the axislabel.
+ return r, pad
+
+
+
+ _default_alignments = dict(left=("center", "right"),
+ right=("center", "left"),
+ bottom=("baseline", "center"),
+ top=("baseline", "center"))
+
+
+
+ # set_default_alignments(self, d)
+
+ _default_angles = dict(left=90,
+ right=-90,
+ bottom=0,
+ top=180)
+
+
+ def draw(self, renderer):
+ if not self.get_visible():
+ self._axislabel_pad = self._get_external_pad()
+ return
+
+ r, total_width = self._get_ticklabels_offsets(renderer,
+ self._axis_direction)
+
+ #self._set_external_pad(r+self._get_external_pad())
+ pad = self._get_external_pad() + \
+ renderer.points_to_pixels(self.get_pad())
+ self._set_offset_radius(r+pad)
+
+ #self._set_offset_radius(r)
+
+ for (x, y), a, l in self._locs_angles_labels:
+ if not l.strip(): continue
+ self._set_ref_angle(a) #+ add_angle
+ self.set_x(x)
+ self.set_y(y)
+ self.set_text(l)
+ LabelBase.draw(self, renderer)
+
+ self._axislabel_pad = total_width \
+ + pad # the value saved will be used to draw axislabel.
+
+
+ def set_locs_angles_labels(self, locs_angles_labels):
+ self._locs_angles_labels = locs_angles_labels
+
+ def get_window_extents(self, renderer):
+
+ if not self.get_visible():
+ self._axislabel_pad = self._get_external_pad()
+ return []
+
+ bboxes = []
+
+ r, total_width = self._get_ticklabels_offsets(renderer,
+ self._axis_direction)
+
+ pad = self._get_external_pad() + \
+ renderer.points_to_pixels(self.get_pad())
+ self._set_offset_radius(r+pad)
+
+
+ for (x, y), a, l in self._locs_angles_labels:
+ self._set_ref_angle(a) #+ add_angle
+ self.set_x(x)
+ self.set_y(y)
+ self.set_text(l)
+ bb = LabelBase.get_window_extent(self, renderer)
+ bboxes.append(bb)
+
+ self._axislabel_pad = total_width \
+ + pad # the value saved will be used to draw axislabel.
+
+ return bboxes
+
+
+ def get_texts_widths_heights_descents(self, renderer):
+ """
+ return a list of width, height, descent for ticklabels.
+ """
+ whd_list = []
+ for (x, y), a, l in self._locs_angles_labels:
+ if not l.strip(): continue
+ clean_line, ismath = self.is_math_text(l)
+ whd = renderer.get_text_width_height_descent(
+ clean_line, self._fontproperties, ismath=ismath)
+ whd_list.append(whd)
+
+ return whd_list
+
+
+class GridlinesCollection(LineCollection):
+ def __init__(self, *kl, **kwargs):
+ """
+ *which* : "major" or "minor"
+ *axis* : "both", "x" or "y"
+ """
+ self._which = kwargs.pop("which", "major")
+ self._axis = kwargs.pop("axis", "both")
+ super(GridlinesCollection, self).__init__(*kl, **kwargs)
+ self.set_grid_helper(None)
+
+ def set_which(self, which):
+ self._which = which
+
+ def set_axis(self, axis):
+ self._axis = axis
+
+ def set_grid_helper(self, grid_helper):
+ self._grid_helper = grid_helper
+
+ def draw(self, renderer):
+ if self._grid_helper is not None:
+ self._grid_helper.update_lim(self.axes)
+ gl = self._grid_helper.get_gridlines(self._which, self._axis)
+ if gl:
+ self.set_segments([np.transpose(l) for l in gl])
+ else:
+ self.set_segments([])
+ super(GridlinesCollection, self).draw(renderer)
+
+
+
+
+class AxisArtist(martist.Artist):
+ """
+ An artist which draws axis (a line along which the n-th axes coord
+ is constant) line, ticks, ticklabels, and axis label.
+ """
+
+ ZORDER=2.5
+
+ @property
+ def LABELPAD(self):
+ return self.label.get_pad()
+
+ @LABELPAD.setter
+ def LABELPAD(self, v):
+ return self.label.set_pad(v)
+
+ def __init__(self, axes,
+ helper,
+ offset=None,
+ axis_direction="bottom",
+ **kw):
+ """
+ *axes* : axes
+ *helper* : an AxisArtistHelper instance.
+ """
+ #axes is also used to follow the axis attribute (tick color, etc).
+
+ super(AxisArtist, self).__init__(**kw)
+
+ self.axes = axes
+
+ self._axis_artist_helper = helper
+
+ if offset is None:
+ offset = (0, 0)
+ self.dpi_transform = Affine2D()
+ self.offset_transform = ScaledTranslation(offset[0], offset[1],
+ self.dpi_transform)
+
+ self._label_visible = True
+ self._majortick_visible = True
+ self._majorticklabel_visible = True
+ self._minortick_visible = True
+ self._minorticklabel_visible = True
+
+
+ #if self._axis_artist_helper._loc in ["left", "right"]:
+ if axis_direction in ["left", "right"]:
+ axis_name = "ytick"
+ self.axis = axes.yaxis
+ else:
+ axis_name = "xtick"
+ self.axis = axes.xaxis
+
+
+ self._axisline_style = None
+
+
+ self._axis_direction = axis_direction
+
+
+ self._init_line()
+ self._init_ticks(axis_name, **kw)
+ self._init_offsetText(axis_direction)
+ self._init_label()
+
+ self.set_zorder(self.ZORDER)
+
+ self._rotate_label_along_line = False
+
+ # axis direction
+ self._tick_add_angle = 180.
+ self._ticklabel_add_angle = 0.
+ self._axislabel_add_angle = 0.
+ self.set_axis_direction(axis_direction)
+
+
+ # axis direction
+
+ def set_axis_direction(self, axis_direction):
+ """
+ Adjust the direction, text angle, text alignment of
+ ticklabels, labels following the matplotlib convention for
+ the rectangle axes.
+
+ The *axis_direction* must be one of [left, right, bottom,
+ top].
+
+ ===================== ========== ========= ========== ==========
+ property left bottom right top
+ ===================== ========== ========= ========== ==========
+ ticklabels location "-" "+" "+" "-"
+ axislabel location "-" "+" "+" "-"
+ ticklabels angle 90 0 -90 180
+ ticklabel va center baseline center baseline
+ ticklabel ha right center right center
+ axislabel angle 180 0 0 180
+ axislabel va center top center bottom
+ axislabel ha right center right center
+ ===================== ========== ========= ========== ==========
+
+
+ Note that the direction "+" and "-" are relative to the direction of
+ the increasing coordinate. Also, the text angles are actually
+ relative to (90 + angle of the direction to the ticklabel),
+ which gives 0 for bottom axis.
+
+ """
+
+ if axis_direction not in ["left", "right", "top", "bottom"]:
+ raise ValueError('direction must be on of "left", "right", "top", "bottom"')
+ self._axis_direction = axis_direction
+ if axis_direction in ["left", "top"]:
+ #self._set_tick_direction("+")
+ self.set_ticklabel_direction("-")
+ self.set_axislabel_direction("-")
+ else:
+ #self._set_tick_direction("-")
+ self.set_ticklabel_direction("+")
+ self.set_axislabel_direction("+")
+
+ self.major_ticklabels.set_axis_direction(axis_direction)
+ self.label.set_axis_direction(axis_direction)
+
+ # def _set_tick_direction(self, d):
+ # if d not in ["+", "-"]:
+ # raise ValueError('direction must be on of "in", "out"')
+
+ # if d == "+":
+ # self._tick_add_angle = 0 #get_helper()._extremes=0, 10
+ # else:
+ # self._tick_add_angle = 180 #get_helper()._extremes=0, 10
+
+ def set_ticklabel_direction(self, tick_direction):
+ """
+ Adjust the direction of the ticklabel.
+
+ ACCEPTS: [ "+" | "-" ]
+
+ Note that the label_direction '+' and '-' are relative to the
+ direction of the increasing coordinate.
+ """
+
+ if tick_direction not in ["+", "-"]:
+ raise ValueError('direction must be one of "+", "-"')
+
+ if tick_direction == "-":
+ self._ticklabel_add_angle = 180
+ else:
+ self._ticklabel_add_angle = 0
+
+ def invert_ticklabel_direction(self):
+ self._ticklabel_add_angle = (self._ticklabel_add_angle + 180) % 360
+ self.major_ticklabels.invert_axis_direction()
+ self.minor_ticklabels.invert_axis_direction()
+
+ # def invert_ticks_direction(self):
+ # self.major_ticks.set_tick_out(not self.major_ticks.get_tick_out())
+ # self.minor_ticks.set_tick_out(not self.minor_ticks.get_tick_out())
+
+ def set_axislabel_direction(self, label_direction):
+ """
+ Adjust the direction of the axislabel.
+
+ ACCEPTS: [ "+" | "-" ]
+
+ Note that the label_direction '+' and '-' are relative to the
+ direction of the increasing coordinate.
+ """
+ if label_direction not in ["+", "-"]:
+ raise ValueError('direction must be one of "+", "-"')
+
+ if label_direction == "-":
+ self._axislabel_add_angle = 180
+ else:
+ self._axislabel_add_angle = 0
+
+
+
+ def get_transform(self):
+ return self.axes.transAxes + self.offset_transform
+
+ def get_helper(self):
+ """
+ Return axis artist helper instance.
+ """
+ return self._axis_artist_helper
+
+
+ def set_axisline_style(self, axisline_style=None, **kw):
+ """
+ Set the axisline style.
+
+ *axisline_style* can be a string with axisline style name with optional
+ comma-separated attributes. Alternatively, the attrs can
+ be provided as keywords.
+
+ set_arrowstyle("->,size=1.5")
+ set_arrowstyle("->", size=1.5)
+
+ Old attrs simply are forgotten.
+
+ Without argument (or with arrowstyle=None), return
+ available styles as a list of strings.
+ """
+
+ if axisline_style==None:
+ return AxislineStyle.pprint_styles()
+
+ if isinstance(axisline_style, AxislineStyle._Base):
+ self._axisline_style = axisline_style
+ else:
+ self._axisline_style = AxislineStyle(axisline_style, **kw)
+
+
+ self._init_line()
+
+
+ def get_axisline_style(self):
+ """
+ return the current axisline style.
+ """
+ return self._axisline_style
+
+ def _init_line(self):
+ """
+ Initialize the *line* artist that is responsible to draw the axis line.
+ """
+ tran = self._axis_artist_helper.get_line_transform(self.axes) \
+ + self.offset_transform
+
+ axisline_style = self.get_axisline_style()
+ if axisline_style is None:
+ self.line = BezierPath(self._axis_artist_helper.get_line(self.axes),
+ color=rcParams['axes.edgecolor'],
+ linewidth=rcParams['axes.linewidth'],
+ transform=tran)
+ else:
+ self.line = axisline_style(self, transform=tran)
+
+ def _draw_line(self, renderer):
+ self.line.set_path(self._axis_artist_helper.get_line(self.axes))
+ if self.get_axisline_style() is not None:
+ self.line.set_line_mutation_scale(self.major_ticklabels.get_size())
+ self.line.draw(renderer)
+
+
+ def _init_ticks(self, axis_name, **kw):
+
+ trans=self._axis_artist_helper.get_tick_transform(self.axes) \
+ + self.offset_transform
+
+
+ major_tick_size = kw.get("major_tick_size",
+ rcParams['%s.major.size'%axis_name])
+ major_tick_pad = kw.get("major_tick_pad",
+ rcParams['%s.major.pad'%axis_name])
+ minor_tick_size = kw.get("minor_tick_size",
+ rcParams['%s.minor.size'%axis_name])
+ minor_tick_pad = kw.get("minor_tick_pad",
+ rcParams['%s.minor.pad'%axis_name])
+
+ self.major_ticks = Ticks(major_tick_size,
+ axis=self.axis,
+ transform=trans)
+ self.minor_ticks = Ticks(minor_tick_size,
+ axis=self.axis,
+ transform=trans)
+
+ if axis_name == "xaxis":
+ size = rcParams['xtick.labelsize']
+ else:
+ size = rcParams['ytick.labelsize']
+
+
+ fontprops = font_manager.FontProperties(size=size)
+
+ self.major_ticklabels = TickLabels(size=size, axis=self.axis,
+ axis_direction=self._axis_direction)
+ self.minor_ticklabels = TickLabels(size=size, axis=self.axis,
+ axis_direction=self._axis_direction)
+
+
+ self.major_ticklabels.set(figure = self.axes.figure,
+ transform=trans,
+ fontproperties=fontprops)
+ self.major_ticklabels.set_pad(major_tick_pad)
+
+ self.minor_ticklabels.set(figure = self.axes.figure,
+ transform=trans,
+ fontproperties=fontprops)
+ self.minor_ticklabels.set_pad(minor_tick_pad)
+
+
+
+ def _get_tick_info(self, tick_iter):
+ """
+ return ticks_loc_angle, ticklabels_loc_angle_label
+
+ ticks_loc_angle : list of locs and angles for ticks
+ ticklabels_loc_angle_label : list of locs, angles and labels for tickslabels
+ """
+ ticks_loc_angle = []
+ ticklabels_loc_angle_label = []
+
+ tick_add_angle = self._tick_add_angle
+ ticklabel_add_angle = self._ticklabel_add_angle
+
+ for loc, angle_normal, angle_tangent, label in tick_iter:
+ angle_label = angle_tangent - 90
+ angle_label += ticklabel_add_angle
+
+ if np.cos((angle_label - angle_normal)/180.*np.pi) < 0.:
+ angle_tick = angle_normal
+ else:
+ angle_tick = angle_normal + 180
+
+ ticks_loc_angle.append([loc, angle_tick])
+ ticklabels_loc_angle_label.append([loc, angle_label, label])
+
+ return ticks_loc_angle, ticklabels_loc_angle_label
+
+
+ def _update_ticks(self, renderer):
+
+
+ # set extra pad for major and minor ticklabels:
+ # use ticksize of majorticks even for minor ticks. not clear what is best.
+
+ dpi_cor = renderer.points_to_pixels(1.)
+ if self.major_ticks.get_visible() and self.major_ticks.get_tick_out():
+ self.major_ticklabels._set_external_pad(self.major_ticks._ticksize*dpi_cor)
+ self.minor_ticklabels._set_external_pad(self.major_ticks._ticksize*dpi_cor)
+ else:
+ self.major_ticklabels._set_external_pad(0)
+ self.minor_ticklabels._set_external_pad(0)
+
+
+ majortick_iter, minortick_iter = \
+ self._axis_artist_helper.get_tick_iterators(self.axes)
+
+ tick_loc_angle, ticklabel_loc_angle_label \
+ = self._get_tick_info(majortick_iter)
+
+ self.major_ticks.set_locs_angles(tick_loc_angle)
+ self.major_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label)
+
+ #self.major_ticks.draw(renderer)
+ #self.major_ticklabels.draw(renderer)
+
+
+ # minor ticks
+ tick_loc_angle, ticklabel_loc_angle_label \
+ = self._get_tick_info(minortick_iter)
+
+ self.minor_ticks.set_locs_angles(tick_loc_angle)
+ self.minor_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label)
+
+ #self.minor_ticks.draw(renderer)
+ #self.minor_ticklabels.draw(renderer)
+
+
+ #if (self.major_ticklabels.get_visible() or self.minor_ticklabels.get_visible()):
+ # self._draw_offsetText(renderer)
+
+ return self.major_ticklabels.get_window_extents(renderer)
+
+
+ def _draw_ticks(self, renderer):
+
+ extents = self._update_ticks(renderer)
+
+ self.major_ticks.draw(renderer)
+ self.major_ticklabels.draw(renderer)
+
+ self.minor_ticks.draw(renderer)
+ self.minor_ticklabels.draw(renderer)
+
+
+ if (self.major_ticklabels.get_visible() or self.minor_ticklabels.get_visible()):
+ self._draw_offsetText(renderer)
+
+ return extents
+
+ def _draw_ticks2(self, renderer):
+
+
+ # set extra pad for major and minor ticklabels:
+ # use ticksize of majorticks even for minor ticks. not clear what is best.
+
+ dpi_cor = renderer.points_to_pixels(1.)
+ if self.major_ticks.get_visible() and self.major_ticks.get_tick_out():
+ self.major_ticklabels._set_external_pad(self.major_ticks._ticksize*dpi_cor)
+ self.minor_ticklabels._set_external_pad(self.major_ticks._ticksize*dpi_cor)
+ else:
+ self.major_ticklabels._set_external_pad(0)
+ self.minor_ticklabels._set_external_pad(0)
+
+
+ majortick_iter, minortick_iter = \
+ self._axis_artist_helper.get_tick_iterators(self.axes)
+
+ tick_loc_angle, ticklabel_loc_angle_label \
+ = self._get_tick_info(majortick_iter)
+
+ self.major_ticks.set_locs_angles(tick_loc_angle)
+ self.major_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label)
+
+ self.major_ticks.draw(renderer)
+ self.major_ticklabels.draw(renderer)
+
+
+ # minor ticks
+ tick_loc_angle, ticklabel_loc_angle_label \
+ = self._get_tick_info(minortick_iter)
+
+ self.minor_ticks.set_locs_angles(tick_loc_angle)
+ self.minor_ticklabels.set_locs_angles_labels(ticklabel_loc_angle_label)
+
+ self.minor_ticks.draw(renderer)
+ self.minor_ticklabels.draw(renderer)
+
+
+ if (self.major_ticklabels.get_visible() or self.minor_ticklabels.get_visible()):
+ self._draw_offsetText(renderer)
+
+ return self.major_ticklabels.get_window_extents(renderer)
+
+
+
+
+ _offsetText_pos = dict(left=(0, 1, "bottom", "right"),
+ right=(1, 1, "bottom", "left"),
+ bottom=(1, 0, "top", "right"),
+ top=(1, 1, "bottom", "right"))
+
+ def _init_offsetText(self, direction):
+
+ x,y,va,ha = self._offsetText_pos[direction]
+
+ self.offsetText = mtext.Annotation("",
+ xy=(x,y), xycoords="axes fraction",
+ xytext=(0,0), textcoords="offset points",
+ #fontproperties = fp,
+ color = rcParams['xtick.color'],
+ verticalalignment=va,
+ horizontalalignment=ha,
+ )
+ self.offsetText.set_transform(IdentityTransform())
+ self.axes._set_artist_props(self.offsetText)
+
+
+ def _update_offsetText(self):
+ self.offsetText.set_text( self.axis.major.formatter.get_offset() )
+ self.offsetText.set_size(self.major_ticklabels.get_size())
+ offset = self.major_ticklabels.get_pad() + self.major_ticklabels.get_size() + 2.
+ self.offsetText.xyann= (0, offset)
+
+
+ def _draw_offsetText(self, renderer):
+ self._update_offsetText()
+ self.offsetText.draw(renderer)
+
+
+
+ def _init_label(self, **kw):
+ # x in axes coords, y in display coords (to be updated at draw
+ # time by _update_label_positions)
+
+ labelsize = kw.get("labelsize",
+ rcParams['axes.labelsize'])
+ #labelcolor = kw.get("labelcolor",
+ # rcParams['axes.labelcolor'])
+ fontprops = font_manager.FontProperties(
+ size=labelsize,
+ weight=rcParams['axes.labelweight'])
+ textprops = dict(fontproperties = fontprops)
+ #color = labelcolor)
+
+ tr = self._axis_artist_helper.get_axislabel_transform(self.axes) \
+ + self.offset_transform
+
+ self.label = AxisLabel(0, 0, "__from_axes__",
+ color = "auto", #rcParams['axes.labelcolor'],
+ fontproperties=fontprops,
+ axis=self.axis,
+ transform=tr,
+ axis_direction=self._axis_direction,
+ )
+
+ self.label.set_figure(self.axes.figure)
+
+ labelpad = kw.get("labelpad", 5)
+ self.label.set_pad(labelpad)
+
+
+ def _update_label(self, renderer):
+
+ if not self.label.get_visible():
+ return
+
+ fontprops = font_manager.FontProperties(
+ size=rcParams['axes.labelsize'],
+ weight=rcParams['axes.labelweight'])
+
+ #pad_points = self.major_tick_pad
+
+ #if abs(self._ticklabel_add_angle - self._axislabel_add_angle)%360 > 90:
+ if self._ticklabel_add_angle != self._axislabel_add_angle:
+ if (self.major_ticks.get_visible() and not self.major_ticks.get_tick_out()) \
+ or \
+ (self.minor_ticks.get_visible() and not self.major_ticks.get_tick_out()):
+ axislabel_pad = self.major_ticks._ticksize
+ else:
+ axislabel_pad = 0
+ else:
+ axislabel_pad = max(self.major_ticklabels._axislabel_pad,
+ self.minor_ticklabels._axislabel_pad)
+
+
+ #label_offset = axislabel_pad + self.LABELPAD
+
+ #self.label._set_offset_radius(label_offset)
+ self.label._set_external_pad(axislabel_pad)
+
+ xy, angle_tangent = self._axis_artist_helper.get_axislabel_pos_angle(self.axes)
+ if xy is None: return
+
+ angle_label = angle_tangent - 90
+
+
+ x, y = xy
+ self.label._set_ref_angle(angle_label+self._axislabel_add_angle)
+ self.label.set(x=x, y=y)
+
+
+ def _draw_label(self, renderer):
+ self._update_label(renderer)
+ self.label.draw(renderer)
+
+ def _draw_label2(self, renderer):
+
+ if not self.label.get_visible():
+ return
+
+ fontprops = font_manager.FontProperties(
+ size=rcParams['axes.labelsize'],
+ weight=rcParams['axes.labelweight'])
+
+ #pad_points = self.major_tick_pad
+
+ #if abs(self._ticklabel_add_angle - self._axislabel_add_angle)%360 > 90:
+ if self._ticklabel_add_angle != self._axislabel_add_angle:
+ if (self.major_ticks.get_visible() and not self.major_ticks.get_tick_out()) \
+ or \
+ (self.minor_ticks.get_visible() and not self.major_ticks.get_tick_out()):
+ axislabel_pad = self.major_ticks._ticksize
+ else:
+ axislabel_pad = 0
+ else:
+ axislabel_pad = max(self.major_ticklabels._axislabel_pad,
+ self.minor_ticklabels._axislabel_pad)
+
+ #label_offset = axislabel_pad + self.LABELPAD
+
+ #self.label._set_offset_radius(label_offset)
+ self.label._set_external_pad(axislabel_pad)
+
+ xy, angle_tangent = self._axis_artist_helper.get_axislabel_pos_angle(self.axes)
+ if xy is None: return
+
+ angle_label = angle_tangent - 90
+
+ x, y = xy
+ self.label._set_ref_angle(angle_label+self._axislabel_add_angle)
+ self.label.set(x=x, y=y)
+ self.label.draw(renderer)
+
+
+
+ def set_label(self, s):
+ self.label.set_text(s)
+
+
+
+ def get_tightbbox(self, renderer):
+ if not self.get_visible(): return
+
+ self._axis_artist_helper.update_lim(self.axes)
+
+ dpi_cor = renderer.points_to_pixels(1.)
+ self.dpi_transform.clear().scale(dpi_cor, dpi_cor)
+
+
+ bb = []
+
+ self._update_ticks(renderer)
+
+ #if self.major_ticklabels.get_visible():
+ bb.extend(self.major_ticklabels.get_window_extents(renderer))
+ #if self.minor_ticklabels.get_visible():
+ bb.extend(self.minor_ticklabels.get_window_extents(renderer))
+
+
+ self._update_label(renderer)
+
+ #if self.label.get_visible():
+ bb.append(self.label.get_window_extent(renderer))
+ bb.append(self.offsetText.get_window_extent(renderer))
+
+ bb = [b for b in bb if b and (b.width!=0 or b.height!=0)]
+ if bb:
+ _bbox = Bbox.union(bb)
+ return _bbox
+ else:
+ return None
+
+ #self._draw_line(renderer)
+
+ #self._draw_ticks(renderer)
+
+ #self._draw_offsetText(renderer)
+ #self._draw_label(renderer)
+
+
+
+ @allow_rasterization
+ def draw(self, renderer):
+ 'Draw the axis lines, tick lines and labels'
+
+ if not self.get_visible(): return
+
+ renderer.open_group(__name__)
+
+ self._axis_artist_helper.update_lim(self.axes)
+
+ dpi_cor = renderer.points_to_pixels(1.)
+ self.dpi_transform.clear().scale(dpi_cor, dpi_cor)
+
+
+ self._draw_ticks(renderer)
+
+ self._draw_line(renderer)
+
+ #self._draw_offsetText(renderer)
+ self._draw_label(renderer)
+
+ renderer.close_group(__name__)
+
+ #def get_ticklabel_extents(self, renderer):
+ # pass
+
+ def toggle(self, all=None, ticks=None, ticklabels=None, label=None):
+ """
+ Toggle visibility of ticks, ticklabels, and (axis) label.
+ To turn all off, ::
+
+ axis.toggle(all=False)
+
+ To turn all off but ticks on ::
+
+ axis.toggle(all=False, ticks=True)
+
+ To turn all on but (axis) label off ::
+
+ axis.toggle(all=True, label=False))
+
+ """
+ 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
+
+ if _ticks is not None:
+ self.major_ticks.set_visible(_ticks)
+ self.minor_ticks.set_visible(_ticks)
+ if _ticklabels is not None:
+ self.major_ticklabels.set_visible(_ticklabels)
+ self.minor_ticklabels.set_visible(_ticklabels)
+ if _label is not None:
+ self.label.set_visible(_label)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axisline_style.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axisline_style.py
new file mode 100644
index 0000000000..876f5fe189
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axisline_style.py
@@ -0,0 +1,168 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+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(AxislineStyle._Base, self).__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(AxislineStyle.SimpleArrow, self).__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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axislines.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axislines.py
new file mode 100644
index 0000000000..6182608cc5
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axislines.py
@@ -0,0 +1,828 @@
+"""
+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.
+
+"""
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+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(AxisArtistHelper.Fixed, self).__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(AxisArtistHelper.Floating,
+ self).__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(AxisArtistHelperRectlinear.Fixed, self).__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(AxisArtistHelperRectlinear.Floating, self).__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.:
+ if 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(GridHelperBase, self).__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(GridHelperRectlinear, self).__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 self.axes.xaxis._gridOnMajor:
+ if which in ["both", "major"]:
+ locs.extend(self.axes.xaxis.major.locator())
+ #if self.axes.xaxis._gridOnMinor:
+ 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:
+ #if which in ["both", "major"]:
+ locs.extend(self.axes.yaxis.major.locator())
+ if self.axes.yaxis._gridOnMinor:
+ #if which in ["both", "minor"]:
+ 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(Axes.AxisDict, self).__init__()
+
+ def __getitem__(self, k):
+ if isinstance(k, tuple):
+ r = SimpleChainedObjects([dict.__getitem__(self, k1) for k1 in k])
+ return r
+ elif isinstance(k, slice):
+ if k.start == None and k.stop == None and k.step == None:
+ r = SimpleChainedObjects(list(six.itervalues(self)))
+ return r
+ 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__(self, *kl, **kw):
+
+
+ helper = kw.pop("grid_helper", None)
+
+ self._axisline_on = True
+
+ if helper:
+ self._grid_helper = helper
+ else:
+ self._grid_helper = GridHelperRectlinear(self)
+
+ super(Axes, self).__init__(*kl, **kw)
+
+ 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(self):
+ super(Axes, self)._init_axis()
+
+
+ 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.
+ gridlines = self.new_gridlines(grid_helper)
+
+ self.gridlines = gridlines
+
+ def cla(self):
+ # gridlines need to b created before cla() since cla calls grid()
+
+ self._init_gridlines()
+ super(Axes, self).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(Axes, self).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 = list(six.itervalues(self._axislines)) + [self.gridlines]
+ else:
+ children = []
+ children.extend(super(Axes, self).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
+
+
+
+ def draw(self, renderer, inframe=False):
+
+ if not self._axisline_on:
+ super(Axes, self).draw(renderer, inframe)
+ return
+
+ orig_artists = self.artists
+ self.artists = self.artists + list(self._axislines.values()) + [self.gridlines]
+
+ super(Axes, self).draw(renderer, inframe)
+
+ self.artists = orig_artists
+
+
+ def get_tightbbox(self, renderer, call_axes_locator=True):
+
+ bb0 = super(Axes, self).get_tightbbox(renderer, call_axes_locator)
+
+ if not self._axisline_on:
+ return bb0
+
+ bb = [bb0]
+
+ for axisline in list(six.itervalues(self._axislines)):
+ if not axisline.get_visible():
+ continue
+
+ bb.append(axisline.get_tightbbox(renderer))
+ # if axisline.label.get_visible():
+ # bb.append(axisline.label.get_window_extent(renderer))
+
+
+ # if axisline.major_ticklabels.get_visible():
+ # bb.extend(axisline.major_ticklabels.get_window_extents(renderer))
+ # if axisline.minor_ticklabels.get_visible():
+ # bb.extend(axisline.minor_ticklabels.get_window_extents(renderer))
+ # if axisline.major_ticklabels.get_visible() or \
+ # axisline.minor_ticklabels.get_visible():
+ # bb.append(axisline.offsetText.get_window_extent(renderer))
+
+ #bb.extend([c.get_window_extent(renderer) for c in artists \
+ # if c.get_visible()])
+
+ _bbox = Bbox.union([b for b in bb if b and (b.width!=0 or b.height!=0)])
+
+ return _bbox
+
+
+
+
+Subplot = maxes.subplot_class_factory(Axes)
+
+class AxesZero(Axes):
+ def __init__(self, *kl, **kw):
+
+ super(AxesZero, self).__init__(*kl, **kw)
+
+
+ def _init_axis_artists(self):
+ super(AxesZero, self)._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)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/clip_path.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/clip_path.py
new file mode 100644
index 0000000000..8507b09b07
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/clip_path.py
@@ -0,0 +1,135 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+from six.moves import zip
+
+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 = []
+
+ if xdir:
+ xsign = 1
+ else:
+ xsign = -1
+
+ if ydir:
+ ysign = 1
+ else:
+ ysign = -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:]]))
+
+ #clipped_pos_angles.append(_pos_angles)
+
+
+ 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-180) for (x, y, a) in c_left_ \
+ # if bbox.containsy(y)]
+ 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]
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/floating_axes.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/floating_axes.py
new file mode 100644
index 0000000000..468413dbac
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/floating_axes.py
@@ -0,0 +1,544 @@
+"""
+An experimental support for curvilinear grid.
+"""
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+from six.moves import zip
+
+# 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(FixedAxisArtistHelper, self).__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 = d/3.14159*180.
+ d2 = d2/3.14159*180.
+ #_mod = (d2-d1+180)%360
+ #if _mod < 180:
+ # d1 += 180
+ ##_div, _mod = divmod(d2-d1, 360)
+ yield [x, y], d1, d2, lab
+ #, d2/3.14159*180.+da)
+
+ 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(GridHelperCurveLinear, self).__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)
+
+
+
+_floatingaxes_classes = {}
+
+def floatingaxes_class_factory(axes_class):
+
+ new_class = _floatingaxes_classes.get(axes_class)
+ if new_class is None:
+ new_class = type(str("Floating %s" % (axes_class.__name__)),
+ (FloatingAxesBase, axes_class),
+ {'_axes_class_floating': axes_class})
+ _floatingaxes_classes[axes_class] = new_class
+
+ return new_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)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/grid_finder.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/grid_finder.py
new file mode 100644
index 0000000000..62a94b1478
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/grid_finder.py
@@ -0,0 +1,340 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+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(GridFinderBase, self).__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 = dict()
+ gi["values"] = []
+ gi["levels"] = []
+ gi["tick_levels"] = dict(left=[], bottom=[], right=[], top=[])
+ gi["tick_locs"] = dict(left=[], bottom=[], right=[], top=[])
+ gi["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):
+ x, y = np.asarray(x), np.asarray(y)
+ ll1 = np.concatenate((x[:,np.newaxis], y[:,np.newaxis]), 1)
+ ll2 = aux_trans.transform(ll1)
+ lon, lat = ll2[:,0], ll2[:,1]
+ return lon, lat
+
+ def inv_transform_xy(x, y):
+ x, y = np.asarray(x), np.asarray(y)
+ ll1 = np.concatenate((x[:,np.newaxis], y[:,np.newaxis]), 1)
+ 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(GridFinder, self).__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) and (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(DictFormatter, self).__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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/grid_helper_curvelinear.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/grid_helper_curvelinear.py
new file mode 100644
index 0000000000..578645148e
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/grid_helper_curvelinear.py
@@ -0,0 +1,475 @@
+"""
+An experimental support for curvilinear grid.
+"""
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+from six.moves import zip
+
+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(FixedAxisArtistHelper, self).__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(FloatingAxisArtistHelper, self).__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 = d/3.14159*180.
+ d2 = d2/3.14159*180.
+ 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(GridHelperCurveLinear, self).__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()
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/parasite_axes.py b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/parasite_axes.py
new file mode 100644
index 0000000000..cad56e43a2
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/axisartist/parasite_axes.py
@@ -0,0 +1,18 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+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)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/__init__.py b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/__init__.py
new file mode 100644
index 0000000000..cd9c2139d2
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/__init__.py
@@ -0,0 +1,6 @@
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+from .axes3d import Axes3D
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/art3d.py b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/art3d.py
new file mode 100644
index 0000000000..ef55dd693e
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/art3d.py
@@ -0,0 +1,774 @@
+# 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.
+'''
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+from six.moves import zip
+
+import math
+
+import numpy as np
+
+from matplotlib import (
+ artist, cbook, colors as mcolors, lines, text as mtext, path as mpath)
+from matplotlib.cbook import _backports
+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 angle between -180 and +180"""
+ a = (a + 360) % 360
+ if a > 180:
+ a = a - 360
+ return a
+
+
+def norm_text_angle(a):
+ """Return angle between -90 and +90"""
+ a = (a + 180) % 180
+ if a > 90:
+ a = a - 180
+ return a
+
+
+def get_dir_vector(zdir):
+ 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 (in the future) direction.
+ '''
+
+ def __init__(self, x=0, y=0, z=0, text='', zdir='z', **kwargs):
+ '''
+ *x*, *y*, *z* Position of text
+ *text* Text string to display
+ *zdir* Direction of text
+
+ Keyword arguments are passed onto :func:`~matplotlib.text.Text`.
+ '''
+ 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
+
+ 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 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 = float(zs)
+ zs = [zs for x in xs]
+ except TypeError:
+ pass
+ self._verts3d = juggle_axes(xs, ys, zs, zdir)
+ self.stale = True
+
+ 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 = _backports.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 = _backports.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 = _backports.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 = _backports.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 __init__(self, segments, *args, **kwargs):
+ '''
+ Keyword arguments are passed onto :func:`~matplotlib.collections.LineCollection`.
+ '''
+ LineCollection.__init__(self, segments, *args, **kwargs)
+
+ 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
+
+ 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, **kwargs):
+ zs = kwargs.pop('zs', [])
+ zdir = kwargs.pop('zdir', 'z')
+ Patch.__init__(self, *args, **kwargs)
+ self.set_3d_properties(zs, zdir)
+
+ def set_3d_properties(self, verts, zs=0, zdir='z'):
+ zs = _backports.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)
+
+ def draw(self, renderer):
+ Patch.draw(self, renderer)
+
+
+class PathPatch3D(Patch3D):
+ '''
+ 3D PathPatch object.
+ '''
+
+ def __init__(self, path, **kwargs):
+ zs = kwargs.pop('zs', [])
+ zdir = kwargs.pop('zdir', 'z')
+ 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, **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.
+ """
+ zs = kwargs.pop('zs', 0)
+ zdir = kwargs.pop('zdir', 'z')
+ self._depthshade = kwargs.pop('depthshade', True)
+ PatchCollection.__init__(self, *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 = zip(*offsets)
+ 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, **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.
+ """
+ zs = kwargs.pop('zs', 0)
+ zdir = kwargs.pop('zdir', 'z')
+ self._depthshade = kwargs.pop('depthshade', True)
+ PathCollection.__init__(self, *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 = zip(*offsets)
+ 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).
+
+ Keywords:
+
+ *za* The location or locations to place the patches in the
+ collection along the *zdir* axis. Defaults to 0.
+
+ *zdir* The axis in which to place the patches. Default is "z".
+
+ *depthshade* Whether to shade the patches to give a sense of depth.
+ Defaults to *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, **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.
+ '''
+ zsort = kwargs.pop('zsort', True)
+ PolyCollection.__init__(self, verts, *args, **kwargs)
+ self.set_zsort(zsort)
+ self._codes3d = None
+
+ _zsort_functions = {
+ 'average': np.average,
+ 'min': np.min,
+ 'max': np.max,
+ }
+
+ def set_zsort(self, zsort):
+ '''
+ Set z-sorting behaviour:
+ boolean: if True use default 'average'
+ string: 'average', 'min' or 'max'
+ '''
+
+ 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_facecolors(self)
+ self._edgecolors3d = PolyCollection.get_edgecolors(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)
+ set_facecolors = set_facecolor
+
+ def set_edgecolor(self, colors):
+ PolyCollection.set_edgecolor(self, colors)
+ self._edgecolors3d = PolyCollection.get_edgecolor(self)
+ set_edgecolors = set_edgecolor
+
+ def set_alpha(self, alpha):
+ """
+ Set the alpha tranparencies of the collection. *alpha* must be
+ a float or *None*.
+
+ ACCEPTS: 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_facecolors(self):
+ return self._facecolors2d
+ get_facecolor = get_facecolors
+
+ def get_edgecolors(self):
+ return self._edgecolors2d
+ get_edgecolor = get_edgecolors
+
+ def draw(self, renderer):
+ return Collection.draw(self, renderer)
+
+
+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 _backports.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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/axes3d.py b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/axes3d.py
new file mode 100644
index 0000000000..b99a090c62
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/axes3d.py
@@ -0,0 +1,2958 @@
+"""
+axes3d.py, original mplot3d version by John Porter
+Created: 23 Sep 2005
+
+Parts fixed by Reinier Heeres <reinier@heeres.eu>
+Minor additions by Ben Axelrod <baxelrod@coroware.com>
+Significant updates and revisions by Ben Root <ben.v.root@gmail.com>
+
+Module containing Axes3D, an object which can plot 3D objects on a
+2D matplotlib figure.
+"""
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+from six.moves import map, xrange, zip, reduce
+
+import math
+import warnings
+from collections import defaultdict
+
+import numpy as np
+
+import matplotlib.axes as maxes
+import matplotlib.cbook as cbook
+import matplotlib.collections as mcoll
+import matplotlib.colors as mcolors
+import matplotlib.docstring as docstring
+import matplotlib.scale as mscale
+import matplotlib.transforms as mtransforms
+from matplotlib.axes import Axes, rcParams
+from matplotlib.cbook import _backports
+from matplotlib.colors import Normalize, LightSource
+from matplotlib.transforms import Bbox
+from matplotlib.tri.triangulation import Triangulation
+
+from . import art3d
+from . import proj3d
+from . import axis3d
+
+
+def unit_bbox():
+ box = Bbox(np.array([[0, 0], [1, 1]]))
+ return box
+
+
+class Axes3D(Axes):
+ """
+ 3D axes object.
+ """
+ name = '3d'
+ _shared_z_axes = cbook.Grouper()
+
+ def __init__(self, fig, rect=None, *args, **kwargs):
+ '''
+ Build an :class:`Axes3D` instance in
+ :class:`~matplotlib.figure.Figure` *fig* with
+ *rect=[left, bottom, width, height]* in
+ :class:`~matplotlib.figure.Figure` coordinates
+
+ Optional keyword arguments:
+
+ ================ =========================================
+ Keyword Description
+ ================ =========================================
+ *azim* Azimuthal viewing angle (default -60)
+ *elev* Elevation viewing angle (default 30)
+ *zscale* [%(scale)s]
+ *sharez* Other axes to share z-limits with
+ *proj_type* 'persp' or 'ortho' (default 'persp')
+ ================ =========================================
+
+ .. versionadded :: 1.2.1
+ *sharez*
+
+ ''' % {'scale': ' | '.join([repr(x) for x in mscale.get_scale_names()])}
+
+ if rect is None:
+ rect = [0.0, 0.0, 1.0, 1.0]
+ self._cids = []
+
+ self.initial_azim = kwargs.pop('azim', -60)
+ self.initial_elev = kwargs.pop('elev', 30)
+ zscale = kwargs.pop('zscale', None)
+ sharez = kwargs.pop('sharez', None)
+ self.set_proj_type(kwargs.pop('proj_type', 'persp'))
+
+ self.xy_viewLim = unit_bbox()
+ self.zz_viewLim = unit_bbox()
+ self.xy_dataLim = unit_bbox()
+ self.zz_dataLim = unit_bbox()
+ # inihibit autoscale_view until the axes are defined
+ # they can't be defined until Axes.__init__ has been called
+ self.view_init(self.initial_elev, self.initial_azim)
+ self._ready = 0
+
+ self._sharez = sharez
+ if sharez is not None:
+ self._shared_z_axes.join(self, sharez)
+ self._adjustable = 'datalim'
+
+ super(Axes3D, self).__init__(fig, rect,
+ frameon=True,
+ *args, **kwargs)
+ # Disable drawing of axes by base class
+ super(Axes3D, self).set_axis_off()
+ # Enable drawing of axes by Axes3D class
+ self.set_axis_on()
+ self.M = None
+
+ # func used to format z -- fall back on major formatters
+ self.fmt_zdata = None
+
+ if zscale is not None:
+ self.set_zscale(zscale)
+
+ if self.zaxis is not None:
+ self._zcid = self.zaxis.callbacks.connect(
+ 'units finalize', lambda: self._on_units_changed(scalez=True))
+ else:
+ self._zcid = None
+
+ self._ready = 1
+ self.mouse_init()
+ self.set_top_view()
+
+ self.patch.set_linewidth(0)
+ # Calculate the pseudo-data width and height
+ pseudo_bbox = self.transLimits.inverted().transform([(0, 0), (1, 1)])
+ self._pseudo_w, self._pseudo_h = pseudo_bbox[1] - pseudo_bbox[0]
+
+ self.figure.add_axes(self)
+
+ def set_axis_off(self):
+ self._axis3don = False
+ self.stale = True
+
+ def set_axis_on(self):
+ self._axis3don = True
+ self.stale = True
+
+ def have_units(self):
+ """
+ Return *True* if units are set on the *x*, *y*, or *z* axes
+
+ """
+ return (self.xaxis.have_units() or self.yaxis.have_units() or
+ self.zaxis.have_units())
+
+ def convert_zunits(self, z):
+ """
+ For artists in an axes, if the zaxis has units support,
+ convert *z* using zaxis unit type
+
+ .. versionadded :: 1.2.1
+
+ """
+ return self.zaxis.convert_units(z)
+
+ def _process_unit_info(self, xdata=None, ydata=None, zdata=None,
+ kwargs=None):
+ """
+ Look for unit *kwargs* and update the axis instances as necessary
+
+ """
+ super(Axes3D, self)._process_unit_info(xdata=xdata, ydata=ydata,
+ kwargs=kwargs)
+
+ if self.xaxis is None or self.yaxis is None or self.zaxis is None:
+ return
+
+ if zdata is not None:
+ # we only need to update if there is nothing set yet.
+ if not self.zaxis.have_units():
+ self.zaxis.update_units(xdata)
+
+ # process kwargs 2nd since these will override default units
+ if kwargs is not None:
+ zunits = kwargs.pop('zunits', self.zaxis.units)
+ if zunits != self.zaxis.units:
+ self.zaxis.set_units(zunits)
+ # If the units being set imply a different converter,
+ # we need to update.
+ if zdata is not None:
+ self.zaxis.update_units(zdata)
+
+ def set_top_view(self):
+ # this happens to be the right view for the viewing coordinates
+ # moved up and to the left slightly to fit labels and axes
+ xdwl = (0.95/self.dist)
+ xdw = (0.9/self.dist)
+ ydwl = (0.95/self.dist)
+ ydw = (0.9/self.dist)
+
+ # This is purposely using the 2D Axes's set_xlim and set_ylim,
+ # because we are trying to place our viewing pane.
+ super(Axes3D, self).set_xlim(-xdwl, xdw, auto=None)
+ super(Axes3D, self).set_ylim(-ydwl, ydw, auto=None)
+
+ def _init_axis(self):
+ '''Init 3D axes; overrides creation of regular X/Y axes'''
+ self.w_xaxis = axis3d.XAxis('x', self.xy_viewLim.intervalx,
+ self.xy_dataLim.intervalx, self)
+ self.xaxis = self.w_xaxis
+ self.w_yaxis = axis3d.YAxis('y', self.xy_viewLim.intervaly,
+ self.xy_dataLim.intervaly, self)
+ self.yaxis = self.w_yaxis
+ self.w_zaxis = axis3d.ZAxis('z', self.zz_viewLim.intervalx,
+ self.zz_dataLim.intervalx, self)
+ self.zaxis = self.w_zaxis
+
+ for ax in self.xaxis, self.yaxis, self.zaxis:
+ ax.init3d()
+
+ def get_children(self):
+ return [self.zaxis, ] + super(Axes3D, self).get_children()
+
+ def _get_axis_list(self):
+ return super(Axes3D, self)._get_axis_list() + (self.zaxis, )
+
+ def unit_cube(self, vals=None):
+ minx, maxx, miny, maxy, minz, maxz = vals or self.get_w_lims()
+ xs, ys, zs = ([minx, maxx, maxx, minx, minx, maxx, maxx, minx],
+ [miny, miny, maxy, maxy, miny, miny, maxy, maxy],
+ [minz, minz, minz, minz, maxz, maxz, maxz, maxz])
+ return list(zip(xs, ys, zs))
+
+ def tunit_cube(self, vals=None, M=None):
+ if M is None:
+ M = self.M
+ xyzs = self.unit_cube(vals)
+ tcube = proj3d.proj_points(xyzs, M)
+ return tcube
+
+ def tunit_edges(self, vals=None, M=None):
+ tc = self.tunit_cube(vals, M)
+ edges = [(tc[0], tc[1]),
+ (tc[1], tc[2]),
+ (tc[2], tc[3]),
+ (tc[3], tc[0]),
+
+ (tc[0], tc[4]),
+ (tc[1], tc[5]),
+ (tc[2], tc[6]),
+ (tc[3], tc[7]),
+
+ (tc[4], tc[5]),
+ (tc[5], tc[6]),
+ (tc[6], tc[7]),
+ (tc[7], tc[4])]
+ return edges
+
+ def draw(self, renderer):
+ # draw the background patch
+ self.patch.draw(renderer)
+ self._frameon = False
+
+ # first, set the aspect
+ # this is duplicated from `axes._base._AxesBase.draw`
+ # but must be called before any of the artist are drawn as
+ # it adjusts the view limits and the size of the bounding box
+ # of the axes
+ locator = self.get_axes_locator()
+ if locator:
+ pos = locator(self, renderer)
+ self.apply_aspect(pos)
+ else:
+ self.apply_aspect()
+
+ # add the projection matrix to the renderer
+ self.M = self.get_proj()
+ renderer.M = self.M
+ renderer.vvec = self.vvec
+ renderer.eye = self.eye
+ renderer.get_axis_position = self.get_axis_position
+
+ # Calculate projection of collections and zorder them
+ for i, col in enumerate(
+ sorted(self.collections,
+ key=lambda col: col.do_3d_projection(renderer),
+ reverse=True)):
+ col.zorder = i
+
+ # Calculate projection of patches and zorder them
+ for i, patch in enumerate(
+ sorted(self.patches,
+ key=lambda patch: patch.do_3d_projection(renderer),
+ reverse=True)):
+ patch.zorder = i
+
+ if self._axis3don:
+ axes = (self.xaxis, self.yaxis, self.zaxis)
+ # Draw panes first
+ for ax in axes:
+ ax.draw_pane(renderer)
+ # Then axes
+ for ax in axes:
+ ax.draw(renderer)
+
+ # Then rest
+ super(Axes3D, self).draw(renderer)
+
+ def get_axis_position(self):
+ vals = self.get_w_lims()
+ tc = self.tunit_cube(vals, self.M)
+ xhigh = tc[1][2] > tc[2][2]
+ yhigh = tc[3][2] > tc[2][2]
+ zhigh = tc[0][2] > tc[2][2]
+ return xhigh, yhigh, zhigh
+
+ def _on_units_changed(self, scalex=False, scaley=False, scalez=False):
+ """
+ Callback for processing changes to axis units.
+
+ Currently forces updates of data limits and view limits.
+ """
+ self.relim()
+ self.autoscale_view(scalex=scalex, scaley=scaley, scalez=scalez)
+
+ def update_datalim(self, xys, **kwargs):
+ pass
+
+ def get_autoscale_on(self):
+ """
+ Get whether autoscaling is applied for all axes on plot commands
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ return super(Axes3D, self).get_autoscale_on() and self.get_autoscalez_on()
+
+ def get_autoscalez_on(self):
+ """
+ Get whether autoscaling for the z-axis is applied on plot commands
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ return self._autoscaleZon
+
+ def set_autoscale_on(self, b):
+ """
+ Set whether autoscaling is applied on plot commands
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+
+ Parameters
+ ----------
+ b : bool
+ .. ACCEPTS: bool
+ """
+ super(Axes3D, self).set_autoscale_on(b)
+ self.set_autoscalez_on(b)
+
+ def set_autoscalez_on(self, b):
+ """
+ Set whether autoscaling for the z-axis is applied on plot commands
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+
+ Parameters
+ ----------
+ b : bool
+ .. ACCEPTS: bool
+ """
+ self._autoscaleZon = b
+
+ def set_zmargin(self, m):
+ """
+ Set padding of Z data limits prior to autoscaling.
+
+ *m* times the data interval will be added to each
+ end of that interval before it is used in autoscaling.
+
+ accepts: float in range 0 to 1
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ if m < 0 or m > 1 :
+ raise ValueError("margin must be in range 0 to 1")
+ self._zmargin = m
+ self.stale = True
+
+ def margins(self, *args, **kw):
+ """
+ Convenience method to set or retrieve autoscaling margins.
+
+ signatures::
+ margins()
+
+ returns xmargin, ymargin, zmargin
+
+ ::
+
+ margins(margin)
+
+ margins(xmargin, ymargin, zmargin)
+
+ margins(x=xmargin, y=ymargin, z=zmargin)
+
+ margins(..., tight=False)
+
+ All forms above set the xmargin, ymargin and zmargin
+ parameters. All keyword parameters are optional. A single argument
+ specifies xmargin, ymargin and zmargin. The *tight* parameter
+ is passed to :meth:`autoscale_view`, which is executed after
+ a margin is changed; the default here is *True*, on the
+ assumption that when margins are specified, no additional
+ padding to match tick marks is usually desired. Setting
+ *tight* to *None* will preserve the previous setting.
+
+ Specifying any margin changes only the autoscaling; for example,
+ if *xmargin* is not None, then *xmargin* times the X data
+ interval will be added to each end of that interval before
+ it is used in autoscaling.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ if not args and not kw:
+ return self._xmargin, self._ymargin, self._zmargin
+
+ tight = kw.pop('tight', True)
+ mx = kw.pop('x', None)
+ my = kw.pop('y', None)
+ mz = kw.pop('z', None)
+ if not args:
+ pass
+ elif len(args) == 1:
+ mx = my = mz = args[0]
+ elif len(args) == 2:
+ warnings.warn(
+ "Passing exactly two positional arguments to Axes3D.margins "
+ "is deprecated. If needed, pass them as keyword arguments "
+ "instead", cbook.mplDeprecation)
+ mx, my = args
+ elif len(args) == 3:
+ mx, my, mz = args
+ else:
+ raise ValueError(
+ "Axes3D.margins takes at most three positional arguments")
+ if mx is not None:
+ self.set_xmargin(mx)
+ if my is not None:
+ self.set_ymargin(my)
+ if mz is not None:
+ self.set_zmargin(mz)
+
+ scalex = mx is not None
+ scaley = my is not None
+ scalez = mz is not None
+
+ self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley,
+ scalez=scalez)
+
+ def autoscale(self, enable=True, axis='both', tight=None):
+ """
+ Convenience method for simple axis view autoscaling.
+ See :meth:`matplotlib.axes.Axes.autoscale` for full explanation.
+ Note that this function behaves the same, but for all
+ three axes. Therefore, 'z' can be passed for *axis*,
+ and 'both' applies to all three axes.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ if enable is None:
+ scalex = True
+ scaley = True
+ scalez = True
+ else:
+ if axis in ['x', 'both']:
+ self._autoscaleXon = scalex = bool(enable)
+ else:
+ scalex = False
+ if axis in ['y', 'both']:
+ self._autoscaleYon = scaley = bool(enable)
+ else:
+ scaley = False
+ if axis in ['z', 'both']:
+ self._autoscaleZon = scalez = bool(enable)
+ else:
+ scalez = False
+ self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley,
+ scalez=scalez)
+
+ def auto_scale_xyz(self, X, Y, Z=None, had_data=None):
+ x, y, z = map(np.asarray, (X, Y, Z))
+ try:
+ x, y = x.flatten(), y.flatten()
+ if Z is not None:
+ z = z.flatten()
+ except AttributeError:
+ raise
+
+ # This updates the bounding boxes as to keep a record as
+ # to what the minimum sized rectangular volume holds the
+ # data.
+ self.xy_dataLim.update_from_data_xy(np.array([x, y]).T, not had_data)
+ if z is not None:
+ self.zz_dataLim.update_from_data_xy(np.array([z, z]).T, not had_data)
+
+ # Let autoscale_view figure out how to use this data.
+ self.autoscale_view()
+
+ def autoscale_view(self, tight=None, scalex=True, scaley=True,
+ scalez=True):
+ """
+ Autoscale the view limits using the data limits.
+ See :meth:`matplotlib.axes.Axes.autoscale_view` for documentation.
+ Note that this function applies to the 3D axes, and as such
+ adds the *scalez* to the function arguments.
+
+ .. versionchanged :: 1.1.0
+ Function signature was changed to better match the 2D version.
+ *tight* is now explicitly a kwarg and placed first.
+
+ .. versionchanged :: 1.2.1
+ This is now fully functional.
+
+ """
+ if not self._ready:
+ return
+
+ # This method looks at the rectangular volume (see above)
+ # of data and decides how to scale the view portal to fit it.
+ if tight is None:
+ # if image data only just use the datalim
+ _tight = self._tight or (len(self.images)>0 and
+ len(self.lines)==0 and
+ len(self.patches)==0)
+ else:
+ _tight = self._tight = bool(tight)
+
+ if scalex and self._autoscaleXon:
+ xshared = self._shared_x_axes.get_siblings(self)
+ dl = [ax.dataLim for ax in xshared]
+ bb = mtransforms.BboxBase.union(dl)
+ x0, x1 = self.xy_dataLim.intervalx
+ xlocator = self.xaxis.get_major_locator()
+ try:
+ x0, x1 = xlocator.nonsingular(x0, x1)
+ except AttributeError:
+ x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False,
+ expander=0.05)
+ if self._xmargin > 0:
+ delta = (x1 - x0) * self._xmargin
+ x0 -= delta
+ x1 += delta
+ if not _tight:
+ x0, x1 = xlocator.view_limits(x0, x1)
+ self.set_xbound(x0, x1)
+
+ if scaley and self._autoscaleYon:
+ yshared = self._shared_y_axes.get_siblings(self)
+ dl = [ax.dataLim for ax in yshared]
+ bb = mtransforms.BboxBase.union(dl)
+ y0, y1 = self.xy_dataLim.intervaly
+ ylocator = self.yaxis.get_major_locator()
+ try:
+ y0, y1 = ylocator.nonsingular(y0, y1)
+ except AttributeError:
+ y0, y1 = mtransforms.nonsingular(y0, y1, increasing=False,
+ expander=0.05)
+ if self._ymargin > 0:
+ delta = (y1 - y0) * self._ymargin
+ y0 -= delta
+ y1 += delta
+ if not _tight:
+ y0, y1 = ylocator.view_limits(y0, y1)
+ self.set_ybound(y0, y1)
+
+ if scalez and self._autoscaleZon:
+ zshared = self._shared_z_axes.get_siblings(self)
+ dl = [ax.dataLim for ax in zshared]
+ bb = mtransforms.BboxBase.union(dl)
+ z0, z1 = self.zz_dataLim.intervalx
+ zlocator = self.zaxis.get_major_locator()
+ try:
+ z0, z1 = zlocator.nonsingular(z0, z1)
+ except AttributeError:
+ z0, z1 = mtransforms.nonsingular(z0, z1, increasing=False,
+ expander=0.05)
+ if self._zmargin > 0:
+ delta = (z1 - z0) * self._zmargin
+ z0 -= delta
+ z1 += delta
+ if not _tight:
+ z0, z1 = zlocator.view_limits(z0, z1)
+ self.set_zbound(z0, z1)
+
+ def get_w_lims(self):
+ '''Get 3D world limits.'''
+ minx, maxx = self.get_xlim3d()
+ miny, maxy = self.get_ylim3d()
+ minz, maxz = self.get_zlim3d()
+ return minx, maxx, miny, maxy, minz, maxz
+
+ def _determine_lims(self, xmin=None, xmax=None, *args, **kwargs):
+ if xmax is None and cbook.iterable(xmin):
+ xmin, xmax = xmin
+ if xmin == xmax:
+ xmin -= 0.05
+ xmax += 0.05
+ return (xmin, xmax)
+
+ def set_xlim3d(self, left=None, right=None, emit=True, auto=False, **kw):
+ """
+ Set 3D x limits.
+
+ See :meth:`matplotlib.axes.Axes.set_xlim` for full documentation.
+
+ """
+ if 'xmin' in kw:
+ left = kw.pop('xmin')
+ if 'xmax' in kw:
+ right = kw.pop('xmax')
+ if kw:
+ raise ValueError("unrecognized kwargs: %s" % list(kw))
+
+ if right is None and cbook.iterable(left):
+ left, right = left
+
+ self._process_unit_info(xdata=(left, right))
+ left = self._validate_converted_limits(left, self.convert_xunits)
+ right = self._validate_converted_limits(right, self.convert_xunits)
+
+ old_left, old_right = self.get_xlim()
+ if left is None:
+ left = old_left
+ if right is None:
+ right = old_right
+
+ if left == right:
+ warnings.warn(('Attempting to set identical left==right results\n'
+ 'in singular transformations; automatically expanding.\n'
+ 'left=%s, right=%s') % (left, right))
+ left, right = mtransforms.nonsingular(left, right, increasing=False)
+ left, right = self.xaxis.limit_range_for_scale(left, right)
+ self.xy_viewLim.intervalx = (left, right)
+
+ if auto is not None:
+ self._autoscaleXon = bool(auto)
+
+ if emit:
+ self.callbacks.process('xlim_changed', self)
+ # Call all of the other x-axes that are shared with this one
+ for other in self._shared_x_axes.get_siblings(self):
+ if other is not self:
+ other.set_xlim(self.xy_viewLim.intervalx,
+ emit=False, auto=auto)
+ if (other.figure != self.figure and
+ other.figure.canvas is not None):
+ other.figure.canvas.draw_idle()
+ self.stale = True
+ return left, right
+ set_xlim = set_xlim3d
+
+ def set_ylim3d(self, bottom=None, top=None, emit=True, auto=False, **kw):
+ """
+ Set 3D y limits.
+
+ See :meth:`matplotlib.axes.Axes.set_ylim` for full documentation.
+
+ """
+ if 'ymin' in kw:
+ bottom = kw.pop('ymin')
+ if 'ymax' in kw:
+ top = kw.pop('ymax')
+ if kw:
+ raise ValueError("unrecognized kwargs: %s" % list(kw))
+
+ if top is None and cbook.iterable(bottom):
+ bottom, top = bottom
+
+ self._process_unit_info(ydata=(bottom, top))
+ bottom = self._validate_converted_limits(bottom, self.convert_yunits)
+ top = self._validate_converted_limits(top, self.convert_yunits)
+
+ old_bottom, old_top = self.get_ylim()
+ if bottom is None:
+ bottom = old_bottom
+ if top is None:
+ top = old_top
+
+ if top == bottom:
+ warnings.warn(('Attempting to set identical bottom==top results\n'
+ 'in singular transformations; automatically expanding.\n'
+ 'bottom=%s, top=%s') % (bottom, top))
+ bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
+ bottom, top = self.yaxis.limit_range_for_scale(bottom, top)
+ self.xy_viewLim.intervaly = (bottom, top)
+
+ if auto is not None:
+ self._autoscaleYon = bool(auto)
+
+ if emit:
+ self.callbacks.process('ylim_changed', self)
+ # Call all of the other y-axes that are shared with this one
+ for other in self._shared_y_axes.get_siblings(self):
+ if other is not self:
+ other.set_ylim(self.xy_viewLim.intervaly,
+ emit=False, auto=auto)
+ if (other.figure != self.figure and
+ other.figure.canvas is not None):
+ other.figure.canvas.draw_idle()
+ self.stale = True
+ return bottom, top
+ set_ylim = set_ylim3d
+
+ def set_zlim3d(self, bottom=None, top=None, emit=True, auto=False, **kw):
+ """
+ Set 3D z limits.
+
+ See :meth:`matplotlib.axes.Axes.set_ylim` for full documentation
+
+ """
+ if 'zmin' in kw:
+ bottom = kw.pop('zmin')
+ if 'zmax' in kw:
+ top = kw.pop('zmax')
+ if kw:
+ raise ValueError("unrecognized kwargs: %s" % list(kw))
+
+ if top is None and cbook.iterable(bottom):
+ bottom, top = bottom
+
+ self._process_unit_info(zdata=(bottom, top))
+ bottom = self._validate_converted_limits(bottom, self.convert_zunits)
+ top = self._validate_converted_limits(top, self.convert_zunits)
+
+ old_bottom, old_top = self.get_zlim()
+ if bottom is None:
+ bottom = old_bottom
+ if top is None:
+ top = old_top
+
+ if top == bottom:
+ warnings.warn(('Attempting to set identical bottom==top results\n'
+ 'in singular transformations; automatically expanding.\n'
+ 'bottom=%s, top=%s') % (bottom, top))
+ bottom, top = mtransforms.nonsingular(bottom, top, increasing=False)
+ bottom, top = self.zaxis.limit_range_for_scale(bottom, top)
+ self.zz_viewLim.intervalx = (bottom, top)
+
+ if auto is not None:
+ self._autoscaleZon = bool(auto)
+
+ if emit:
+ self.callbacks.process('zlim_changed', self)
+ # Call all of the other y-axes that are shared with this one
+ for other in self._shared_z_axes.get_siblings(self):
+ if other is not self:
+ other.set_zlim(self.zz_viewLim.intervalx,
+ emit=False, auto=auto)
+ if (other.figure != self.figure and
+ other.figure.canvas is not None):
+ other.figure.canvas.draw_idle()
+ self.stale = True
+ return bottom, top
+ set_zlim = set_zlim3d
+
+ def get_xlim3d(self):
+ return tuple(self.xy_viewLim.intervalx)
+ get_xlim3d.__doc__ = maxes.Axes.get_xlim.__doc__
+ get_xlim = get_xlim3d
+ if get_xlim.__doc__ is not None:
+ get_xlim.__doc__ += """
+ .. versionchanged :: 1.1.0
+ This function now correctly refers to the 3D x-limits
+ """
+
+ def get_ylim3d(self):
+ return tuple(self.xy_viewLim.intervaly)
+ get_ylim3d.__doc__ = maxes.Axes.get_ylim.__doc__
+ get_ylim = get_ylim3d
+ if get_ylim.__doc__ is not None:
+ get_ylim.__doc__ += """
+ .. versionchanged :: 1.1.0
+ This function now correctly refers to the 3D y-limits.
+ """
+
+ def get_zlim3d(self):
+ '''Get 3D z limits.'''
+ return tuple(self.zz_viewLim.intervalx)
+ get_zlim = get_zlim3d
+
+ def get_zscale(self):
+ """
+ Return the zaxis scale string %s
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """ % (", ".join(mscale.get_scale_names()))
+ return self.zaxis.get_scale()
+
+ # We need to slightly redefine these to pass scalez=False
+ # to their calls of autoscale_view.
+ def set_xscale(self, value, **kwargs):
+ self.xaxis._set_scale(value, **kwargs)
+ self.autoscale_view(scaley=False, scalez=False)
+ self._update_transScale()
+ if maxes.Axes.set_xscale.__doc__ is not None:
+ set_xscale.__doc__ = maxes.Axes.set_xscale.__doc__ + """
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+
+ def set_yscale(self, value, **kwargs):
+ self.yaxis._set_scale(value, **kwargs)
+ self.autoscale_view(scalex=False, scalez=False)
+ self._update_transScale()
+ self.stale = True
+ if maxes.Axes.set_yscale.__doc__ is not None:
+ set_yscale.__doc__ = maxes.Axes.set_yscale.__doc__ + """
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+
+ @docstring.dedent_interpd
+ def set_zscale(self, value, **kwargs):
+ """
+ Set the scaling of the z-axis: %(scale)s
+
+ ACCEPTS: [%(scale)s]
+
+ Different kwargs are accepted, depending on the scale:
+ %(scale_docs)s
+
+ .. note ::
+ Currently, Axes3D objects only supports linear scales.
+ Other scales may or may not work, and support for these
+ is improving with each release.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ self.zaxis._set_scale(value, **kwargs)
+ self.autoscale_view(scalex=False, scaley=False)
+ self._update_transScale()
+ self.stale = True
+
+ def set_zticks(self, *args, **kwargs):
+ """
+ Set z-axis tick locations.
+ See :meth:`matplotlib.axes.Axes.set_yticks` for more details.
+
+ .. note::
+ Minor ticks are not supported.
+
+ .. versionadded:: 1.1.0
+ """
+ return self.zaxis.set_ticks(*args, **kwargs)
+
+ def get_zticks(self, minor=False):
+ """
+ Return the z ticks as a list of locations
+ See :meth:`matplotlib.axes.Axes.get_yticks` for more details.
+
+ .. note::
+ Minor ticks are not supported.
+
+ .. versionadded:: 1.1.0
+ """
+ return self.zaxis.get_ticklocs(minor=minor)
+
+ def get_zmajorticklabels(self):
+ """
+ Get the ztick labels as a list of Text instances
+
+ .. versionadded :: 1.1.0
+ """
+ return cbook.silent_list('Text zticklabel',
+ self.zaxis.get_majorticklabels())
+
+ def get_zminorticklabels(self):
+ """
+ Get the ztick labels as a list of Text instances
+
+ .. note::
+ Minor ticks are not supported. This function was added
+ only for completeness.
+
+ .. versionadded :: 1.1.0
+ """
+ return cbook.silent_list('Text zticklabel',
+ self.zaxis.get_minorticklabels())
+
+ def set_zticklabels(self, *args, **kwargs):
+ """
+ Set z-axis tick labels.
+ See :meth:`matplotlib.axes.Axes.set_yticklabels` for more details.
+
+ .. note::
+ Minor ticks are not supported by Axes3D objects.
+
+ .. versionadded:: 1.1.0
+ """
+ return self.zaxis.set_ticklabels(*args, **kwargs)
+
+ def get_zticklabels(self, minor=False):
+ """
+ Get ztick labels as a list of Text instances.
+ See :meth:`matplotlib.axes.Axes.get_yticklabels` for more details.
+
+ .. note::
+ Minor ticks are not supported.
+
+ .. versionadded:: 1.1.0
+ """
+ return cbook.silent_list('Text zticklabel',
+ self.zaxis.get_ticklabels(minor=minor))
+
+ def zaxis_date(self, tz=None):
+ """
+ Sets up z-axis ticks and labels that treat the z data as dates.
+
+ *tz* is a timezone string or :class:`tzinfo` instance.
+ Defaults to rc value.
+
+ .. note::
+ This function is merely provided for completeness.
+ Axes3D objects do not officially support dates for ticks,
+ and so this may or may not work as expected.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ self.zaxis.axis_date(tz)
+
+ def get_zticklines(self):
+ """
+ Get ztick lines as a list of Line2D instances.
+ Note that this function is provided merely for completeness.
+ These lines are re-calculated as the display changes.
+
+ .. versionadded:: 1.1.0
+ """
+ return self.zaxis.get_ticklines()
+
+ def clabel(self, *args, **kwargs):
+ """
+ This function is currently not implemented for 3D axes.
+ Returns *None*.
+ """
+ return None
+
+ def view_init(self, elev=None, azim=None):
+ """
+ Set the elevation and azimuth of the axes.
+
+ This can be used to rotate the axes programmatically.
+
+ 'elev' stores the elevation angle in the z plane.
+ 'azim' stores the azimuth angle in the x,y plane.
+
+ if elev or azim are None (default), then the initial value
+ is used which was specified in the :class:`Axes3D` constructor.
+ """
+
+ self.dist = 10
+
+ if elev is None:
+ self.elev = self.initial_elev
+ else:
+ self.elev = elev
+
+ if azim is None:
+ self.azim = self.initial_azim
+ else:
+ self.azim = azim
+
+ def set_proj_type(self, proj_type):
+ """
+ Set the projection type.
+
+ Parameters
+ ----------
+ proj_type : str
+ Type of projection, accepts 'persp' and 'ortho'.
+
+ """
+ if proj_type == 'persp':
+ self._projection = proj3d.persp_transformation
+ elif proj_type == 'ortho':
+ self._projection = proj3d.ortho_transformation
+ else:
+ raise ValueError("unrecognized projection: %s" % proj_type)
+
+ def get_proj(self):
+ """
+ Create the projection matrix from the current viewing position.
+
+ elev stores the elevation angle in the z plane
+ azim stores the azimuth angle in the x,y plane
+
+ dist is the distance of the eye viewing point from the object
+ point.
+
+ """
+ relev, razim = np.pi * self.elev/180, np.pi * self.azim/180
+
+ xmin, xmax = self.get_xlim3d()
+ ymin, ymax = self.get_ylim3d()
+ zmin, zmax = self.get_zlim3d()
+
+ # transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
+ worldM = proj3d.world_transformation(xmin, xmax,
+ ymin, ymax,
+ zmin, zmax)
+
+ # look into the middle of the new coordinates
+ R = np.array([0.5, 0.5, 0.5])
+
+ xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
+ yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
+ zp = R[2] + np.sin(relev) * self.dist
+ E = np.array((xp, yp, zp))
+
+ self.eye = E
+ self.vvec = R - E
+ self.vvec = self.vvec / proj3d.mod(self.vvec)
+
+ if abs(relev) > np.pi/2:
+ # upside down
+ V = np.array((0, 0, -1))
+ else:
+ V = np.array((0, 0, 1))
+ zfront, zback = -self.dist, self.dist
+
+ viewM = proj3d.view_transformation(E, R, V)
+ projM = self._projection(zfront, zback)
+ M0 = np.dot(viewM, worldM)
+ M = np.dot(projM, M0)
+ return M
+
+ def mouse_init(self, rotate_btn=1, zoom_btn=3):
+ """Initializes mouse button callbacks to enable 3D rotation of
+ the axes. Also optionally sets the mouse buttons for 3D rotation
+ and zooming.
+
+ ============ =======================================================
+ Argument Description
+ ============ =======================================================
+ *rotate_btn* The integer or list of integers specifying which mouse
+ button or buttons to use for 3D rotation of the axes.
+ Default = 1.
+
+ *zoom_btn* The integer or list of integers specifying which mouse
+ button or buttons to use to zoom the 3D axes.
+ Default = 3.
+ ============ =======================================================
+
+ """
+ self.button_pressed = None
+ canv = self.figure.canvas
+ if canv is not None:
+ c1 = canv.mpl_connect('motion_notify_event', self._on_move)
+ c2 = canv.mpl_connect('button_press_event', self._button_press)
+ c3 = canv.mpl_connect('button_release_event', self._button_release)
+ self._cids = [c1, c2, c3]
+ else:
+ warnings.warn(
+ "Axes3D.figure.canvas is 'None', mouse rotation disabled. "
+ "Set canvas then call Axes3D.mouse_init().")
+
+ # coerce scalars into array-like, then convert into
+ # a regular list to avoid comparisons against None
+ # which breaks in recent versions of numpy.
+ self._rotate_btn = np.atleast_1d(rotate_btn).tolist()
+ self._zoom_btn = np.atleast_1d(zoom_btn).tolist()
+
+ def can_zoom(self):
+ """
+ Return *True* if this axes supports the zoom box button functionality.
+
+ 3D axes objects do not use the zoom box button.
+ """
+ return False
+
+ def can_pan(self):
+ """
+ Return *True* if this axes supports the pan/zoom button functionality.
+
+ 3D axes objects do not use the pan/zoom button.
+ """
+ return False
+
+ def cla(self):
+ """
+ Clear axes
+ """
+ # Disabling mouse interaction might have been needed a long
+ # time ago, but I can't find a reason for it now - BVR (2012-03)
+ #self.disable_mouse_rotation()
+ super(Axes3D, self).cla()
+ self.zaxis.cla()
+
+ if self._sharez is not None:
+ self.zaxis.major = self._sharez.zaxis.major
+ self.zaxis.minor = self._sharez.zaxis.minor
+ z0, z1 = self._sharez.get_zlim()
+ self.set_zlim(z0, z1, emit=False, auto=None)
+ self.zaxis._set_scale(self._sharez.zaxis.get_scale())
+ else:
+ self.zaxis._set_scale('linear')
+ try:
+ self.set_zlim(0, 1)
+ except TypeError:
+ pass
+
+ self._autoscaleZon = True
+ self._zmargin = 0
+
+ self.grid(rcParams['axes3d.grid'])
+
+ def disable_mouse_rotation(self):
+ """Disable mouse button callbacks.
+ """
+ # Disconnect the various events we set.
+ for cid in self._cids:
+ self.figure.canvas.mpl_disconnect(cid)
+
+ self._cids = []
+
+ def _button_press(self, event):
+ if event.inaxes == self:
+ self.button_pressed = event.button
+ self.sx, self.sy = event.xdata, event.ydata
+
+ def _button_release(self, event):
+ self.button_pressed = None
+
+ def format_zdata(self, z):
+ """
+ Return *z* string formatted. This function will use the
+ :attr:`fmt_zdata` attribute if it is callable, else will fall
+ back on the zaxis major formatter
+ """
+ try: return self.fmt_zdata(z)
+ except (AttributeError, TypeError):
+ func = self.zaxis.get_major_formatter().format_data_short
+ val = func(z)
+ return val
+
+ def format_coord(self, xd, yd):
+ """
+ Given the 2D view coordinates attempt to guess a 3D coordinate.
+ Looks for the nearest edge to the point and then assumes that
+ the point is at the same z location as the nearest point on the edge.
+ """
+
+ if self.M is None:
+ return ''
+
+ if self.button_pressed in self._rotate_btn:
+ return 'azimuth=%d deg, elevation=%d deg ' % (self.azim, self.elev)
+ # ignore xd and yd and display angles instead
+
+ # nearest edge
+ p0, p1 = min(self.tunit_edges(),
+ key=lambda edge: proj3d.line2d_seg_dist(
+ edge[0], edge[1], (xd, yd)))
+
+ # scale the z value to match
+ x0, y0, z0 = p0
+ x1, y1, z1 = p1
+ d0 = np.hypot(x0-xd, y0-yd)
+ d1 = np.hypot(x1-xd, y1-yd)
+ dt = d0+d1
+ z = d1/dt * z0 + d0/dt * z1
+
+ x, y, z = proj3d.inv_transform(xd, yd, z, self.M)
+
+ xs = self.format_xdata(x)
+ ys = self.format_ydata(y)
+ zs = self.format_zdata(z)
+ return 'x=%s, y=%s, z=%s' % (xs, ys, zs)
+
+ def _on_move(self, event):
+ """Mouse moving
+
+ button-1 rotates by default. Can be set explicitly in mouse_init().
+ button-3 zooms by default. Can be set explicitly in mouse_init().
+ """
+
+ if not self.button_pressed:
+ return
+
+ if self.M is None:
+ return
+
+ x, y = event.xdata, event.ydata
+ # In case the mouse is out of bounds.
+ if x is None:
+ return
+
+ dx, dy = x - self.sx, y - self.sy
+ w = self._pseudo_w
+ h = self._pseudo_h
+ self.sx, self.sy = x, y
+
+ # Rotation
+ if self.button_pressed in self._rotate_btn:
+ # rotate viewing point
+ # get the x and y pixel coords
+ if dx == 0 and dy == 0:
+ return
+ self.elev = art3d.norm_angle(self.elev - (dy/h)*180)
+ self.azim = art3d.norm_angle(self.azim - (dx/w)*180)
+ self.get_proj()
+ self.stale = True
+ self.figure.canvas.draw_idle()
+
+# elif self.button_pressed == 2:
+ # pan view
+ # project xv,yv,zv -> xw,yw,zw
+ # pan
+# pass
+
+ # Zoom
+ elif self.button_pressed in self._zoom_btn:
+ # zoom view
+ # hmmm..this needs some help from clipping....
+ minx, maxx, miny, maxy, minz, maxz = self.get_w_lims()
+ df = 1-((h - dy)/h)
+ dx = (maxx-minx)*df
+ dy = (maxy-miny)*df
+ dz = (maxz-minz)*df
+ self.set_xlim3d(minx - dx, maxx + dx)
+ self.set_ylim3d(miny - dy, maxy + dy)
+ self.set_zlim3d(minz - dz, maxz + dz)
+ self.get_proj()
+ self.figure.canvas.draw_idle()
+
+ def set_zlabel(self, zlabel, fontdict=None, labelpad=None, **kwargs):
+ '''
+ Set zlabel. See doc for :meth:`set_ylabel` for description.
+
+ '''
+ if labelpad is not None : self.zaxis.labelpad = labelpad
+ return self.zaxis.set_label_text(zlabel, fontdict, **kwargs)
+
+ def get_zlabel(self):
+ """
+ Get the z-label text string.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ label = self.zaxis.get_label()
+ return label.get_text()
+
+ #### Axes rectangle characteristics
+
+ def get_frame_on(self):
+ """
+ Get whether the 3D axes panels are drawn.
+
+ .. versionadded :: 1.1.0
+ """
+ return self._frameon
+
+ def set_frame_on(self, b):
+ """
+ Set whether the 3D axes panels are drawn.
+
+ .. versionadded :: 1.1.0
+
+ Parameters
+ ----------
+ b : bool
+ .. ACCEPTS: bool
+ """
+ self._frameon = bool(b)
+ self.stale = True
+
+ def get_axisbelow(self):
+ """
+ Get whether axis below is true or not.
+
+ For axes3d objects, this will always be *True*
+
+ .. versionadded :: 1.1.0
+ This function was added for completeness.
+ """
+ return True
+
+ def set_axisbelow(self, b):
+ """
+ Set whether axis ticks and gridlines are above or below most artists.
+
+ For axes3d objects, this will ignore any settings and just use *True*
+
+ .. versionadded :: 1.1.0
+ This function was added for completeness.
+
+ Parameters
+ ----------
+ b : bool
+ .. ACCEPTS: bool
+ """
+ self._axisbelow = True
+ self.stale = True
+
+ def grid(self, b=True, **kwargs):
+ '''
+ Set / unset 3D grid.
+
+ .. note::
+
+ Currently, this function does not behave the same as
+ :meth:`matplotlib.axes.Axes.grid`, but it is intended to
+ eventually support that behavior.
+
+ .. versionchanged :: 1.1.0
+ This function was changed, but not tested. Please report any bugs.
+ '''
+ # TODO: Operate on each axes separately
+ if len(kwargs):
+ b = True
+ self._draw_grid = cbook._string_to_bool(b)
+ self.stale = True
+
+ def ticklabel_format(self, **kwargs):
+ """
+ Convenience method for manipulating the ScalarFormatter
+ used by default for linear axes in Axed3D objects.
+
+ See :meth:`matplotlib.axes.Axes.ticklabel_format` for full
+ documentation. Note that this version applies to all three
+ axes of the Axes3D object. Therefore, the *axis* argument
+ will also accept a value of 'z' and the value of 'both' will
+ apply to all three axes.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ style = kwargs.pop('style', '').lower()
+ scilimits = kwargs.pop('scilimits', None)
+ useOffset = kwargs.pop('useOffset', None)
+ axis = kwargs.pop('axis', 'both').lower()
+ if scilimits is not None:
+ try:
+ m, n = scilimits
+ m+n+1 # check that both are numbers
+ except (ValueError, TypeError):
+ raise ValueError("scilimits must be a sequence of 2 integers")
+ if style[:3] == 'sci':
+ sb = True
+ elif style in ['plain', 'comma']:
+ sb = False
+ if style == 'plain':
+ cb = False
+ else:
+ cb = True
+ raise NotImplementedError("comma style remains to be added")
+ elif style == '':
+ sb = None
+ else:
+ raise ValueError("%s is not a valid style value")
+ try:
+ if sb is not None:
+ if axis in ['both', 'z']:
+ self.xaxis.major.formatter.set_scientific(sb)
+ if axis in ['both', 'y']:
+ self.yaxis.major.formatter.set_scientific(sb)
+ if axis in ['both', 'z'] :
+ self.zaxis.major.formatter.set_scientific(sb)
+ if scilimits is not None:
+ if axis in ['both', 'x']:
+ self.xaxis.major.formatter.set_powerlimits(scilimits)
+ if axis in ['both', 'y']:
+ self.yaxis.major.formatter.set_powerlimits(scilimits)
+ if axis in ['both', 'z']:
+ self.zaxis.major.formatter.set_powerlimits(scilimits)
+ if useOffset is not None:
+ if axis in ['both', 'x']:
+ self.xaxis.major.formatter.set_useOffset(useOffset)
+ if axis in ['both', 'y']:
+ self.yaxis.major.formatter.set_useOffset(useOffset)
+ if axis in ['both', 'z']:
+ self.zaxis.major.formatter.set_useOffset(useOffset)
+ except AttributeError:
+ raise AttributeError(
+ "This method only works with the ScalarFormatter.")
+
+ def locator_params(self, axis='both', tight=None, **kwargs):
+ """
+ Convenience method for controlling tick locators.
+
+ See :meth:`matplotlib.axes.Axes.locator_params` for full
+ documentation Note that this is for Axes3D objects,
+ therefore, setting *axis* to 'both' will result in the
+ parameters being set for all three axes. Also, *axis*
+ can also take a value of 'z' to apply parameters to the
+ z axis.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ _x = axis in ['x', 'both']
+ _y = axis in ['y', 'both']
+ _z = axis in ['z', 'both']
+ if _x:
+ self.xaxis.get_major_locator().set_params(**kwargs)
+ if _y:
+ self.yaxis.get_major_locator().set_params(**kwargs)
+ if _z:
+ self.zaxis.get_major_locator().set_params(**kwargs)
+ self.autoscale_view(tight=tight, scalex=_x, scaley=_y, scalez=_z)
+
+ def tick_params(self, axis='both', **kwargs):
+ """
+ Convenience method for changing the appearance of ticks and
+ tick labels.
+
+ See :meth:`matplotlib.axes.Axes.tick_params` for more complete
+ documentation.
+
+ The only difference is that setting *axis* to 'both' will
+ mean that the settings are applied to all three axes. Also,
+ the *axis* parameter also accepts a value of 'z', which
+ would mean to apply to only the z-axis.
+
+ Also, because of how Axes3D objects are drawn very differently
+ from regular 2D axes, some of these settings may have
+ ambiguous meaning. For simplicity, the 'z' axis will
+ accept settings as if it was like the 'y' axis.
+
+ .. note::
+ While this function is currently implemented, the core part
+ of the Axes3D object may ignore some of these settings.
+ Future releases will fix this. Priority will be given to
+ those who file bugs.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ super(Axes3D, self).tick_params(axis, **kwargs)
+ if axis in ['z', 'both'] :
+ zkw = dict(kwargs)
+ zkw.pop('top', None)
+ zkw.pop('bottom', None)
+ zkw.pop('labeltop', None)
+ zkw.pop('labelbottom', None)
+ self.zaxis.set_tick_params(**zkw)
+
+ ### data limits, ticks, tick labels, and formatting
+
+ def invert_zaxis(self):
+ """
+ Invert the z-axis.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ bottom, top = self.get_zlim()
+ self.set_zlim(top, bottom, auto=None)
+
+ def zaxis_inverted(self):
+ '''
+ Returns True if the z-axis is inverted.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ '''
+ bottom, top = self.get_zlim()
+ return top < bottom
+
+ def get_zbound(self):
+ """
+ Returns the z-axis numerical bounds where::
+
+ lowerBound < upperBound
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ bottom, top = self.get_zlim()
+ if bottom < top:
+ return bottom, top
+ else:
+ return top, bottom
+
+ def set_zbound(self, lower=None, upper=None):
+ """
+ Set the lower and upper numerical bounds of the z-axis.
+ This method will honor axes inversion regardless of parameter order.
+ It will not change the :attr:`_autoscaleZon` attribute.
+
+ .. versionadded :: 1.1.0
+ This function was added, but not tested. Please report any bugs.
+ """
+ if upper is None and cbook.iterable(lower):
+ lower,upper = lower
+
+ old_lower,old_upper = self.get_zbound()
+
+ if lower is None: lower = old_lower
+ if upper is None: upper = old_upper
+
+ if self.zaxis_inverted():
+ if lower < upper:
+ self.set_zlim(upper, lower, auto=None)
+ else:
+ self.set_zlim(lower, upper, auto=None)
+ else :
+ if lower < upper:
+ self.set_zlim(lower, upper, auto=None)
+ else :
+ self.set_zlim(upper, lower, auto=None)
+
+ def text(self, x, y, z, s, zdir=None, **kwargs):
+ '''
+ Add text to the plot. kwargs will be passed on to Axes.text,
+ except for the `zdir` keyword, which sets the direction to be
+ used as the z direction.
+ '''
+ text = super(Axes3D, self).text(x, y, s, **kwargs)
+ art3d.text_2d_to_3d(text, z, zdir)
+ return text
+
+ text3D = text
+ text2D = Axes.text
+
+ def plot(self, xs, ys, *args, **kwargs):
+ '''
+ Plot 2D or 3D data.
+
+ ========== ================================================
+ Argument Description
+ ========== ================================================
+ *xs*, *ys* x, y coordinates of vertices
+
+ *zs* z value(s), either one for all points or one for
+ each point.
+ *zdir* Which direction to use as z ('x', 'y' or 'z')
+ when plotting a 2D set.
+ ========== ================================================
+
+ Other arguments are passed on to
+ :func:`~matplotlib.axes.Axes.plot`
+ '''
+ had_data = self.has_data()
+
+ # `zs` can be passed positionally or as keyword; checking whether
+ # args[0] is a string matches the behavior of 2D `plot` (via
+ # `_process_plot_var_args`).
+ if args and not isinstance(args[0], six.string_types):
+ zs = args[0]
+ args = args[1:]
+ if 'zs' in kwargs:
+ raise TypeError("plot() for multiple values for argument 'z'")
+ else:
+ zs = kwargs.pop('zs', 0)
+ zdir = kwargs.pop('zdir', 'z')
+
+ # Match length
+ zs = _backports.broadcast_to(zs, len(xs))
+
+ lines = super(Axes3D, self).plot(xs, ys, *args, **kwargs)
+ for line in lines:
+ art3d.line_2d_to_3d(line, zs=zs, zdir=zdir)
+
+ xs, ys, zs = art3d.juggle_axes(xs, ys, zs, zdir)
+ self.auto_scale_xyz(xs, ys, zs, had_data)
+ return lines
+
+ plot3D = plot
+
+ def plot_surface(self, X, Y, Z, *args, **kwargs):
+ """
+ Create a surface plot.
+
+ By default it will be colored in shades of a solid color, but it also
+ supports color mapping by supplying the *cmap* argument.
+
+ .. note::
+
+ The *rcount* and *ccount* kwargs, which both default to 50,
+ determine the maximum number of samples used in each direction. If
+ the input data is larger, it will be downsampled (by slicing) to
+ these numbers of points.
+
+ Parameters
+ ----------
+ X, Y, Z : 2d arrays
+ Data values.
+
+ rcount, ccount : int
+ Maximum number of samples used in each direction. If the input
+ data is larger, it will be downsampled (by slicing) to these
+ numbers of points. Defaults to 50.
+
+ .. versionadded:: 2.0
+
+ rstride, cstride : int
+ Downsampling stride in each direction. These arguments are
+ mutually exclusive with *rcount* and *ccount*. If only one of
+ *rstride* or *cstride* is set, the other defaults to 10.
+
+ 'classic' mode uses a default of ``rstride = cstride = 10`` instead
+ of the new default of ``rcount = ccount = 50``.
+
+ color : color-like
+ Color of the surface patches.
+
+ cmap : Colormap
+ Colormap of the surface patches.
+
+ facecolors : array-like of colors.
+ Colors of each individual patch.
+
+ norm : Normalize
+ Normalization for the colormap.
+
+ vmin, vmax : float
+ Bounds for the normalization.
+
+ shade : bool
+ Whether to shade the face colors.
+
+ **kwargs :
+ Other arguments are forwarded to `.Poly3DCollection`.
+ """
+
+ had_data = self.has_data()
+
+ if Z.ndim != 2:
+ raise ValueError("Argument Z must be 2-dimensional.")
+ # TODO: Support masked arrays
+ X, Y, Z = np.broadcast_arrays(X, Y, Z)
+ rows, cols = Z.shape
+
+ has_stride = 'rstride' in kwargs or 'cstride' in kwargs
+ has_count = 'rcount' in kwargs or 'ccount' in kwargs
+
+ if has_stride and has_count:
+ raise ValueError("Cannot specify both stride and count arguments")
+
+ rstride = kwargs.pop('rstride', 10)
+ cstride = kwargs.pop('cstride', 10)
+ rcount = kwargs.pop('rcount', 50)
+ ccount = kwargs.pop('ccount', 50)
+
+ if rcParams['_internal.classic_mode']:
+ # Strides have priority over counts in classic mode.
+ # So, only compute strides from counts
+ # if counts were explicitly given
+ if has_count:
+ rstride = int(max(np.ceil(rows / rcount), 1))
+ cstride = int(max(np.ceil(cols / ccount), 1))
+ else:
+ # If the strides are provided then it has priority.
+ # Otherwise, compute the strides from the counts.
+ if not has_stride:
+ rstride = int(max(np.ceil(rows / rcount), 1))
+ cstride = int(max(np.ceil(cols / ccount), 1))
+
+ if 'facecolors' in kwargs:
+ fcolors = kwargs.pop('facecolors')
+ else:
+ color = kwargs.pop('color', None)
+ if color is None:
+ color = self._get_lines.get_next_color()
+ color = np.array(mcolors.to_rgba(color))
+ fcolors = None
+
+ cmap = kwargs.get('cmap', None)
+ norm = kwargs.pop('norm', None)
+ vmin = kwargs.pop('vmin', None)
+ vmax = kwargs.pop('vmax', None)
+ linewidth = kwargs.get('linewidth', None)
+ shade = kwargs.pop('shade', cmap is None)
+ lightsource = kwargs.pop('lightsource', None)
+
+ # Shade the data
+ if shade and cmap is not None and fcolors is not None:
+ fcolors = self._shade_colors_lightsource(Z, cmap, lightsource)
+
+ polys = []
+ # Only need these vectors to shade if there is no cmap
+ if cmap is None and shade :
+ totpts = int(np.ceil((rows - 1) / rstride) *
+ np.ceil((cols - 1) / cstride))
+ v1 = np.empty((totpts, 3))
+ v2 = np.empty((totpts, 3))
+ # This indexes the vertex points
+ which_pt = 0
+
+
+ #colset contains the data for coloring: either average z or the facecolor
+ colset = []
+ for rs in xrange(0, rows-1, rstride):
+ for cs in xrange(0, cols-1, cstride):
+ ps = []
+ for a in (X, Y, Z):
+ ztop = a[rs,cs:min(cols, cs+cstride+1)]
+ zleft = a[rs+1:min(rows, rs+rstride+1),
+ min(cols-1, cs+cstride)]
+ zbase = a[min(rows-1, rs+rstride), cs:min(cols, cs+cstride+1):][::-1]
+ zright = a[rs:min(rows-1, rs+rstride):, cs][::-1]
+ z = np.concatenate((ztop, zleft, zbase, zright))
+ ps.append(z)
+
+ # The construction leaves the array with duplicate points, which
+ # are removed here.
+ ps = list(zip(*ps))
+ lastp = np.array([])
+ ps2 = [ps[0]] + [ps[i] for i in xrange(1, len(ps)) if ps[i] != ps[i-1]]
+ avgzsum = sum(p[2] for p in ps2)
+ polys.append(ps2)
+
+ if fcolors is not None:
+ colset.append(fcolors[rs][cs])
+ else:
+ colset.append(avgzsum / len(ps2))
+
+ # Only need vectors to shade if no cmap
+ if cmap is None and shade:
+ i1, i2, i3 = 0, int(len(ps2)/3), int(2*len(ps2)/3)
+ v1[which_pt] = np.array(ps2[i1]) - np.array(ps2[i2])
+ v2[which_pt] = np.array(ps2[i2]) - np.array(ps2[i3])
+ which_pt += 1
+ if cmap is None and shade:
+ normals = np.cross(v1, v2)
+ else :
+ normals = []
+
+ polyc = art3d.Poly3DCollection(polys, *args, **kwargs)
+
+ if fcolors is not None:
+ if shade:
+ colset = self._shade_colors(colset, normals)
+ polyc.set_facecolors(colset)
+ polyc.set_edgecolors(colset)
+ elif cmap:
+ colset = np.array(colset)
+ polyc.set_array(colset)
+ if vmin is not None or vmax is not None:
+ polyc.set_clim(vmin, vmax)
+ if norm is not None:
+ polyc.set_norm(norm)
+ else:
+ if shade:
+ colset = self._shade_colors(color, normals)
+ else:
+ colset = color
+ polyc.set_facecolors(colset)
+
+ self.add_collection(polyc)
+ self.auto_scale_xyz(X, Y, Z, had_data)
+
+ return polyc
+
+ def _generate_normals(self, polygons):
+ '''
+ Generate normals for polygons by using the first three points.
+ This normal of course might not make sense for polygons with
+ more than three points not lying in a plane.
+ '''
+
+ normals = []
+ for verts in polygons:
+ v1 = np.array(verts[0]) - np.array(verts[1])
+ v2 = np.array(verts[2]) - np.array(verts[0])
+ normals.append(np.cross(v1, v2))
+ return normals
+
+ def _shade_colors(self, color, normals):
+ '''
+ Shade *color* using normal vectors given by *normals*.
+ *color* can also be an array of the same length as *normals*.
+ '''
+
+ shade = np.array([np.dot(n / proj3d.mod(n), [-1, -1, 0.5])
+ if proj3d.mod(n) else np.nan
+ for n in normals])
+ mask = ~np.isnan(shade)
+
+ if len(shade[mask]) > 0:
+ norm = Normalize(min(shade[mask]), max(shade[mask]))
+ shade[~mask] = min(shade[mask])
+ color = mcolors.to_rgba_array(color)
+ # shape of color should be (M, 4) (where M is number of faces)
+ # shape of shade should be (M,)
+ # colors should have final shape of (M, 4)
+ alpha = color[:, 3]
+ colors = (0.5 + norm(shade)[:, np.newaxis] * 0.5) * color
+ colors[:, 3] = alpha
+ else:
+ colors = np.asanyarray(color).copy()
+
+ return colors
+
+ def _shade_colors_lightsource(self, data, cmap, lightsource):
+ if lightsource is None:
+ lightsource = LightSource(azdeg=135, altdeg=55)
+ return lightsource.shade(data, cmap)
+
+ def plot_wireframe(self, X, Y, Z, *args, **kwargs):
+ """
+ Plot a 3D wireframe.
+
+ .. note::
+
+ The *rcount* and *ccount* kwargs, which both default to 50,
+ determine the maximum number of samples used in each direction. If
+ the input data is larger, it will be downsampled (by slicing) to
+ these numbers of points.
+
+ Parameters
+ ----------
+ X, Y, Z : 2d arrays
+ Data values.
+
+ rcount, ccount : int
+ Maximum number of samples used in each direction. If the input
+ data is larger, it will be downsampled (by slicing) to these
+ numbers of points. Setting a count to zero causes the data to be
+ not sampled in the corresponding direction, producing a 3D line
+ plot rather than a wireframe plot. Defaults to 50.
+
+ .. versionadded:: 2.0
+
+ rstride, cstride : int
+ Downsampling stride in each direction. These arguments are
+ mutually exclusive with *rcount* and *ccount*. If only one of
+ *rstride* or *cstride* is set, the other defaults to 1. Setting a
+ stride to zero causes the data to be not sampled in the
+ corresponding direction, producing a 3D line plot rather than a
+ wireframe plot.
+
+ 'classic' mode uses a default of ``rstride = cstride = 1`` instead
+ of the new default of ``rcount = ccount = 50``.
+
+ **kwargs :
+ Other arguments are forwarded to `.Line3DCollection`.
+ """
+
+ had_data = self.has_data()
+ if Z.ndim != 2:
+ raise ValueError("Argument Z must be 2-dimensional.")
+ # FIXME: Support masked arrays
+ X, Y, Z = np.broadcast_arrays(X, Y, Z)
+ rows, cols = Z.shape
+
+ has_stride = 'rstride' in kwargs or 'cstride' in kwargs
+ has_count = 'rcount' in kwargs or 'ccount' in kwargs
+
+ if has_stride and has_count:
+ raise ValueError("Cannot specify both stride and count arguments")
+
+ rstride = kwargs.pop('rstride', 1)
+ cstride = kwargs.pop('cstride', 1)
+ rcount = kwargs.pop('rcount', 50)
+ ccount = kwargs.pop('ccount', 50)
+
+ if rcParams['_internal.classic_mode']:
+ # Strides have priority over counts in classic mode.
+ # So, only compute strides from counts
+ # if counts were explicitly given
+ if has_count:
+ rstride = int(max(np.ceil(rows / rcount), 1)) if rcount else 0
+ cstride = int(max(np.ceil(cols / ccount), 1)) if ccount else 0
+ else:
+ # If the strides are provided then it has priority.
+ # Otherwise, compute the strides from the counts.
+ if not has_stride:
+ rstride = int(max(np.ceil(rows / rcount), 1)) if rcount else 0
+ cstride = int(max(np.ceil(cols / ccount), 1)) if ccount else 0
+
+ # We want two sets of lines, one running along the "rows" of
+ # Z and another set of lines running along the "columns" of Z.
+ # This transpose will make it easy to obtain the columns.
+ tX, tY, tZ = np.transpose(X), np.transpose(Y), np.transpose(Z)
+
+ if rstride:
+ rii = list(xrange(0, rows, rstride))
+ # Add the last index only if needed
+ if rows > 0 and rii[-1] != (rows - 1):
+ rii += [rows-1]
+ else:
+ rii = []
+ if cstride:
+ cii = list(xrange(0, cols, cstride))
+ # Add the last index only if needed
+ if cols > 0 and cii[-1] != (cols - 1):
+ cii += [cols-1]
+ else:
+ cii = []
+
+ if rstride == 0 and cstride == 0:
+ raise ValueError("Either rstride or cstride must be non zero")
+
+ # If the inputs were empty, then just
+ # reset everything.
+ if Z.size == 0:
+ rii = []
+ cii = []
+
+ xlines = [X[i] for i in rii]
+ ylines = [Y[i] for i in rii]
+ zlines = [Z[i] for i in rii]
+
+ txlines = [tX[i] for i in cii]
+ tylines = [tY[i] for i in cii]
+ tzlines = [tZ[i] for i in cii]
+
+ lines = ([list(zip(xl, yl, zl))
+ for xl, yl, zl in zip(xlines, ylines, zlines)]
+ + [list(zip(xl, yl, zl))
+ for xl, yl, zl in zip(txlines, tylines, tzlines)])
+
+ linec = art3d.Line3DCollection(lines, *args, **kwargs)
+ self.add_collection(linec)
+ self.auto_scale_xyz(X, Y, Z, had_data)
+
+ return linec
+
+ def plot_trisurf(self, *args, **kwargs):
+ """
+ ============= ================================================
+ Argument Description
+ ============= ================================================
+ *X*, *Y*, *Z* Data values as 1D arrays
+ *color* Color of the surface patches
+ *cmap* A colormap for the surface patches.
+ *norm* An instance of Normalize to map values to colors
+ *vmin* Minimum value to map
+ *vmax* Maximum value to map
+ *shade* Whether to shade the facecolors
+ ============= ================================================
+
+ The (optional) triangulation can be specified in one of two ways;
+ either::
+
+ plot_trisurf(triangulation, ...)
+
+ where triangulation is a :class:`~matplotlib.tri.Triangulation`
+ object, or::
+
+ plot_trisurf(X, Y, ...)
+ plot_trisurf(X, Y, triangles, ...)
+ plot_trisurf(X, Y, triangles=triangles, ...)
+
+ in which case a Triangulation object will be created. See
+ :class:`~matplotlib.tri.Triangulation` for a explanation of
+ these possibilities.
+
+ The remaining arguments are::
+
+ plot_trisurf(..., Z)
+
+ where *Z* is the array of values to contour, one per point
+ in the triangulation.
+
+ Other arguments are passed on to
+ :class:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`
+
+ **Examples:**
+
+ .. plot:: gallery/mplot3d/trisurf3d.py
+ .. plot:: gallery/mplot3d/trisurf3d_2.py
+
+ .. versionadded:: 1.2.0
+ This plotting function was added for the v1.2.0 release.
+ """
+
+ had_data = self.has_data()
+
+ # TODO: Support custom face colours
+ color = kwargs.pop('color', None)
+ if color is None:
+ color = self._get_lines.get_next_color()
+ color = np.array(mcolors.to_rgba(color))
+
+ cmap = kwargs.get('cmap', None)
+ norm = kwargs.pop('norm', None)
+ vmin = kwargs.pop('vmin', None)
+ vmax = kwargs.pop('vmax', None)
+ linewidth = kwargs.get('linewidth', None)
+ shade = kwargs.pop('shade', cmap is None)
+ lightsource = kwargs.pop('lightsource', None)
+
+ tri, args, kwargs = Triangulation.get_from_args_and_kwargs(*args, **kwargs)
+ if 'Z' in kwargs:
+ z = np.asarray(kwargs.pop('Z'))
+ else:
+ z = np.asarray(args[0])
+ # We do this so Z doesn't get passed as an arg to PolyCollection
+ args = args[1:]
+
+ triangles = tri.get_masked_triangles()
+ xt = tri.x[triangles]
+ yt = tri.y[triangles]
+ zt = z[triangles]
+
+ # verts = np.stack((xt, yt, zt), axis=-1)
+ verts = np.concatenate((
+ xt[..., np.newaxis], yt[..., np.newaxis], zt[..., np.newaxis]
+ ), axis=-1)
+
+ polyc = art3d.Poly3DCollection(verts, *args, **kwargs)
+
+ if cmap:
+ # average over the three points of each triangle
+ avg_z = verts[:, :, 2].mean(axis=1)
+ polyc.set_array(avg_z)
+ if vmin is not None or vmax is not None:
+ polyc.set_clim(vmin, vmax)
+ if norm is not None:
+ polyc.set_norm(norm)
+ else:
+ if shade:
+ v1 = verts[:, 0, :] - verts[:, 1, :]
+ v2 = verts[:, 1, :] - verts[:, 2, :]
+ normals = np.cross(v1, v2)
+ colset = self._shade_colors(color, normals)
+ else:
+ colset = color
+ polyc.set_facecolors(colset)
+
+ self.add_collection(polyc)
+ self.auto_scale_xyz(tri.x, tri.y, z, had_data)
+
+ return polyc
+
+ def _3d_extend_contour(self, cset, stride=5):
+ '''
+ Extend a contour in 3D by creating
+ '''
+
+ levels = cset.levels
+ colls = cset.collections
+ dz = (levels[1] - levels[0]) / 2
+
+ for z, linec in zip(levels, colls):
+ topverts = art3d.paths_to_3d_segments(linec.get_paths(), z - dz)
+ botverts = art3d.paths_to_3d_segments(linec.get_paths(), z + dz)
+
+ color = linec.get_color()[0]
+
+ polyverts = []
+ normals = []
+ nsteps = np.round(len(topverts[0]) / stride)
+ if nsteps <= 1:
+ if len(topverts[0]) > 1:
+ nsteps = 2
+ else:
+ continue
+
+ stepsize = (len(topverts[0]) - 1) / (nsteps - 1)
+ for i in range(int(np.round(nsteps)) - 1):
+ i1 = int(np.round(i * stepsize))
+ i2 = int(np.round((i + 1) * stepsize))
+ polyverts.append([topverts[0][i1],
+ topverts[0][i2],
+ botverts[0][i2],
+ botverts[0][i1]])
+
+ v1 = np.array(topverts[0][i1]) - np.array(topverts[0][i2])
+ v2 = np.array(topverts[0][i1]) - np.array(botverts[0][i1])
+ normals.append(np.cross(v1, v2))
+
+ colors = self._shade_colors(color, normals)
+ colors2 = self._shade_colors(color, normals)
+ polycol = art3d.Poly3DCollection(polyverts,
+ facecolors=colors,
+ edgecolors=colors2)
+ polycol.set_sort_zpos(z)
+ self.add_collection3d(polycol)
+
+ for col in colls:
+ self.collections.remove(col)
+
+ def add_contour_set(self, cset, extend3d=False, stride=5, zdir='z', offset=None):
+ zdir = '-' + zdir
+ if extend3d:
+ self._3d_extend_contour(cset, stride)
+ else:
+ for z, linec in zip(cset.levels, cset.collections):
+ if offset is not None:
+ z = offset
+ art3d.line_collection_2d_to_3d(linec, z, zdir=zdir)
+
+ def add_contourf_set(self, cset, zdir='z', offset=None):
+ zdir = '-' + zdir
+ for z, linec in zip(cset.levels, cset.collections):
+ if offset is not None :
+ z = offset
+ art3d.poly_collection_2d_to_3d(linec, z, zdir=zdir)
+ linec.set_sort_zpos(z)
+
+ def contour(self, X, Y, Z, *args, **kwargs):
+ '''
+ Create a 3D contour plot.
+
+ ========== ================================================
+ Argument Description
+ ========== ================================================
+ *X*, *Y*, Data values as numpy.arrays
+ *Z*
+ *extend3d* Whether to extend contour in 3D (default: False)
+ *stride* Stride (step size) for extending contour
+ *zdir* The direction to use: x, y or z (default)
+ *offset* If specified plot a projection of the contour
+ lines on this position in plane normal to zdir
+ ========== ================================================
+
+ The positional and other keyword arguments are passed on to
+ :func:`~matplotlib.axes.Axes.contour`
+
+ Returns a :class:`~matplotlib.axes.Axes.contour`
+ '''
+
+ extend3d = kwargs.pop('extend3d', False)
+ stride = kwargs.pop('stride', 5)
+ zdir = kwargs.pop('zdir', 'z')
+ offset = kwargs.pop('offset', None)
+
+ had_data = self.has_data()
+
+ jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
+ cset = super(Axes3D, self).contour(jX, jY, jZ, *args, **kwargs)
+ self.add_contour_set(cset, extend3d, stride, zdir, offset)
+
+ self.auto_scale_xyz(X, Y, Z, had_data)
+ return cset
+
+ contour3D = contour
+
+ def tricontour(self, *args, **kwargs):
+ """
+ Create a 3D contour plot.
+
+ ========== ================================================
+ Argument Description
+ ========== ================================================
+ *X*, *Y*, Data values as numpy.arrays
+ *Z*
+ *extend3d* Whether to extend contour in 3D (default: False)
+ *stride* Stride (step size) for extending contour
+ *zdir* The direction to use: x, y or z (default)
+ *offset* If specified plot a projection of the contour
+ lines on this position in plane normal to zdir
+ ========== ================================================
+
+ Other keyword arguments are passed on to
+ :func:`~matplotlib.axes.Axes.tricontour`
+
+ Returns a :class:`~matplotlib.axes.Axes.contour`
+
+ .. versionchanged:: 1.3.0
+ Added support for custom triangulations
+
+ EXPERIMENTAL: This method currently produces incorrect output due to a
+ longstanding bug in 3D PolyCollection rendering.
+ """
+
+ extend3d = kwargs.pop('extend3d', False)
+ stride = kwargs.pop('stride', 5)
+ zdir = kwargs.pop('zdir', 'z')
+ offset = kwargs.pop('offset', None)
+
+ had_data = self.has_data()
+
+ tri, args, kwargs = Triangulation.get_from_args_and_kwargs(
+ *args, **kwargs)
+ X = tri.x
+ Y = tri.y
+ if 'Z' in kwargs:
+ Z = kwargs.pop('Z')
+ else:
+ Z = args[0]
+ # We do this so Z doesn't get passed as an arg to Axes.tricontour
+ args = args[1:]
+
+ jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
+ tri = Triangulation(jX, jY, tri.triangles, tri.mask)
+
+ cset = super(Axes3D, self).tricontour(tri, jZ, *args, **kwargs)
+ self.add_contour_set(cset, extend3d, stride, zdir, offset)
+
+ self.auto_scale_xyz(X, Y, Z, had_data)
+ return cset
+
+ def contourf(self, X, Y, Z, *args, **kwargs):
+ '''
+ Create a 3D contourf plot.
+
+ ========== ================================================
+ Argument Description
+ ========== ================================================
+ *X*, *Y*, Data values as numpy.arrays
+ *Z*
+ *zdir* The direction to use: x, y or z (default)
+ *offset* If specified plot a projection of the filled contour
+ on this position in plane normal to zdir
+ ========== ================================================
+
+ The positional and keyword arguments are passed on to
+ :func:`~matplotlib.axes.Axes.contourf`
+
+ Returns a :class:`~matplotlib.axes.Axes.contourf`
+
+ .. versionchanged :: 1.1.0
+ The *zdir* and *offset* kwargs were added.
+ '''
+
+ zdir = kwargs.pop('zdir', 'z')
+ offset = kwargs.pop('offset', None)
+
+ had_data = self.has_data()
+
+ jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
+ cset = super(Axes3D, self).contourf(jX, jY, jZ, *args, **kwargs)
+ self.add_contourf_set(cset, zdir, offset)
+
+ self.auto_scale_xyz(X, Y, Z, had_data)
+ return cset
+
+ contourf3D = contourf
+
+ def tricontourf(self, *args, **kwargs):
+ """
+ Create a 3D contourf plot.
+
+ ========== ================================================
+ Argument Description
+ ========== ================================================
+ *X*, *Y*, Data values as numpy.arrays
+ *Z*
+ *zdir* The direction to use: x, y or z (default)
+ *offset* If specified plot a projection of the contour
+ lines on this position in plane normal to zdir
+ ========== ================================================
+
+ Other keyword arguments are passed on to
+ :func:`~matplotlib.axes.Axes.tricontour`
+
+ Returns a :class:`~matplotlib.axes.Axes.contour`
+
+ .. versionchanged :: 1.3.0
+ Added support for custom triangulations
+
+ EXPERIMENTAL: This method currently produces incorrect output due to a
+ longstanding bug in 3D PolyCollection rendering.
+ """
+ zdir = kwargs.pop('zdir', 'z')
+ offset = kwargs.pop('offset', None)
+
+ had_data = self.has_data()
+
+ tri, args, kwargs = Triangulation.get_from_args_and_kwargs(
+ *args, **kwargs)
+ X = tri.x
+ Y = tri.y
+ if 'Z' in kwargs:
+ Z = kwargs.pop('Z')
+ else:
+ Z = args[0]
+ # We do this so Z doesn't get passed as an arg to Axes.tricontourf
+ args = args[1:]
+
+ jX, jY, jZ = art3d.rotate_axes(X, Y, Z, zdir)
+ tri = Triangulation(jX, jY, tri.triangles, tri.mask)
+
+ cset = super(Axes3D, self).tricontourf(tri, jZ, *args, **kwargs)
+ self.add_contourf_set(cset, zdir, offset)
+
+ self.auto_scale_xyz(X, Y, Z, had_data)
+ return cset
+
+ def add_collection3d(self, col, zs=0, zdir='z'):
+ '''
+ Add a 3D collection object to the plot.
+
+ 2D collection types are converted to a 3D version by
+ modifying the object and adding z coordinate information.
+
+ Supported are:
+ - PolyCollection
+ - LineCollection
+ - PatchCollection
+ '''
+ zvals = np.atleast_1d(zs)
+ if len(zvals) > 0 :
+ zsortval = min(zvals)
+ else :
+ zsortval = 0 # FIXME: Fairly arbitrary. Is there a better value?
+
+ # FIXME: use issubclass() (although, then a 3D collection
+ # object would also pass.) Maybe have a collection3d
+ # abstract class to test for and exclude?
+ if type(col) is mcoll.PolyCollection:
+ art3d.poly_collection_2d_to_3d(col, zs=zs, zdir=zdir)
+ col.set_sort_zpos(zsortval)
+ elif type(col) is mcoll.LineCollection:
+ art3d.line_collection_2d_to_3d(col, zs=zs, zdir=zdir)
+ col.set_sort_zpos(zsortval)
+ elif type(col) is mcoll.PatchCollection:
+ art3d.patch_collection_2d_to_3d(col, zs=zs, zdir=zdir)
+ col.set_sort_zpos(zsortval)
+
+ super(Axes3D, self).add_collection(col)
+
+ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=True,
+ *args, **kwargs):
+ '''
+ Create a scatter plot.
+
+ ============ ========================================================
+ Argument Description
+ ============ ========================================================
+ *xs*, *ys* Positions of data points.
+ *zs* Either an array of the same length as *xs* and
+ *ys* or a single value to place all points in
+ the same plane. Default is 0.
+ *zdir* Which direction to use as z ('x', 'y' or 'z')
+ when plotting a 2D set.
+ *s* Size in points^2. It is a scalar or an array of the
+ same length as *x* and *y*.
+
+ *c* A color. *c* can be a single color format string, or a
+ sequence of color specifications of length *N*, or a
+ sequence of *N* numbers to be mapped to colors using the
+ *cmap* and *norm* specified via kwargs (see below). Note
+ that *c* should not be a single numeric RGB or RGBA
+ sequence because that is indistinguishable from an array
+ of values to be colormapped. *c* can be a 2-D array in
+ which the rows are RGB or RGBA, however, including the
+ case of a single row to specify the same color for
+ all points.
+
+ *depthshade*
+ Whether or not to shade the scatter markers to give
+ the appearance of depth. Default is *True*.
+ ============ ========================================================
+
+ Keyword arguments are passed on to
+ :func:`~matplotlib.axes.Axes.scatter`.
+
+ Returns a :class:`~mpl_toolkits.mplot3d.art3d.Patch3DCollection`
+ '''
+
+ had_data = self.has_data()
+
+ xs, ys, zs = np.broadcast_arrays(
+ *[np.ravel(np.ma.filled(t, np.nan)) for t in [xs, ys, zs]])
+ s = np.ma.ravel(s) # This doesn't have to match x, y in size.
+
+ xs, ys, zs, s, c = cbook.delete_masked_points(xs, ys, zs, s, c)
+
+ patches = super(Axes3D, self).scatter(
+ xs, ys, s=s, c=c, *args, **kwargs)
+ is_2d = not cbook.iterable(zs)
+ zs = _backports.broadcast_to(zs, len(xs))
+ art3d.patch_collection_2d_to_3d(patches, zs=zs, zdir=zdir,
+ depthshade=depthshade)
+
+ if self._zmargin < 0.05 and xs.size > 0:
+ self.set_zmargin(0.05)
+
+ #FIXME: why is this necessary?
+ if not is_2d:
+ self.auto_scale_xyz(xs, ys, zs, had_data)
+
+ return patches
+
+ scatter3D = scatter
+
+ def bar(self, left, height, zs=0, zdir='z', *args, **kwargs):
+ '''
+ Add 2D bar(s).
+
+ ========== ================================================
+ Argument Description
+ ========== ================================================
+ *left* The x coordinates of the left sides of the bars.
+ *height* The height of the bars.
+ *zs* Z coordinate of bars, if one value is specified
+ they will all be placed at the same z.
+ *zdir* Which direction to use as z ('x', 'y' or 'z')
+ when plotting a 2D set.
+ ========== ================================================
+
+ Keyword arguments are passed onto :func:`~matplotlib.axes.Axes.bar`.
+
+ Returns a :class:`~mpl_toolkits.mplot3d.art3d.Patch3DCollection`
+ '''
+
+ had_data = self.has_data()
+
+ patches = super(Axes3D, self).bar(left, height, *args, **kwargs)
+
+ zs = _backports.broadcast_to(zs, len(left))
+
+ verts = []
+ verts_zs = []
+ for p, z in zip(patches, zs):
+ vs = art3d.get_patch_verts(p)
+ verts += vs.tolist()
+ verts_zs += [z] * len(vs)
+ art3d.patch_2d_to_3d(p, z, zdir)
+ if 'alpha' in kwargs:
+ p.set_alpha(kwargs['alpha'])
+
+ if len(verts) > 0 :
+ # the following has to be skipped if verts is empty
+ # NOTE: Bugs could still occur if len(verts) > 0,
+ # but the "2nd dimension" is empty.
+ xs, ys = list(zip(*verts))
+ else :
+ xs, ys = [], []
+
+ xs, ys, verts_zs = art3d.juggle_axes(xs, ys, verts_zs, zdir)
+ self.auto_scale_xyz(xs, ys, verts_zs, had_data)
+
+ return patches
+
+ def bar3d(self, x, y, z, dx, dy, dz, color=None,
+ zsort='average', shade=True, *args, **kwargs):
+ """Generate a 3D barplot.
+
+ This method creates three dimensional barplot where the width,
+ depth, height, and color of the bars can all be uniquely set.
+
+ Parameters
+ ----------
+ x, y, z : array-like
+ The coordinates of the anchor point of the bars.
+
+ dx, dy, dz : scalar or array-like
+ The width, depth, and height of the bars, respectively.
+
+ color : sequence of valid color specifications, optional
+ The color of the bars can be specified globally or
+ individually. This parameter can be:
+
+ - A single color value, to color all bars the same color.
+ - An array of colors of length N bars, to color each bar
+ independently.
+ - An array of colors of length 6, to color the faces of the
+ bars similarly.
+ - An array of colors of length 6 * N bars, to color each face
+ independently.
+
+ When coloring the faces of the boxes specifically, this is
+ the order of the coloring:
+
+ 1. -Z (bottom of box)
+ 2. +Z (top of box)
+ 3. -Y
+ 4. +Y
+ 5. -X
+ 6. +X
+
+ zsort : str, optional
+ The z-axis sorting scheme passed onto
+ :func:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`
+
+ shade : bool, optional (default = True)
+ When true, this shades the dark sides of the bars (relative
+ to the plot's source of light).
+
+ Any additional keyword arguments are passed onto
+ :func:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`
+
+ Returns
+ -------
+ collection : Poly3DCollection
+ A collection of three dimensional polygons representing
+ the bars.
+ """
+
+ had_data = self.has_data()
+
+ x, y, z, dx, dy, dz = np.broadcast_arrays(
+ np.atleast_1d(x), y, z, dx, dy, dz)
+ minx = np.min(x)
+ maxx = np.max(x + dx)
+ miny = np.min(y)
+ maxy = np.max(y + dy)
+ minz = np.min(z)
+ maxz = np.max(z + dz)
+
+ polys = []
+ for xi, yi, zi, dxi, dyi, dzi in zip(x, y, z, dx, dy, dz):
+ polys.extend([
+ ((xi, yi, zi), (xi + dxi, yi, zi),
+ (xi + dxi, yi + dyi, zi), (xi, yi + dyi, zi)),
+ ((xi, yi, zi + dzi), (xi + dxi, yi, zi + dzi),
+ (xi + dxi, yi + dyi, zi + dzi), (xi, yi + dyi, zi + dzi)),
+
+ ((xi, yi, zi), (xi + dxi, yi, zi),
+ (xi + dxi, yi, zi + dzi), (xi, yi, zi + dzi)),
+ ((xi, yi + dyi, zi), (xi + dxi, yi + dyi, zi),
+ (xi + dxi, yi + dyi, zi + dzi), (xi, yi + dyi, zi + dzi)),
+
+ ((xi, yi, zi), (xi, yi + dyi, zi),
+ (xi, yi + dyi, zi + dzi), (xi, yi, zi + dzi)),
+ ((xi + dxi, yi, zi), (xi + dxi, yi + dyi, zi),
+ (xi + dxi, yi + dyi, zi + dzi), (xi + dxi, yi, zi + dzi)),
+ ])
+
+ facecolors = []
+ if color is None:
+ color = [self._get_patches_for_fill.get_next_color()]
+
+ if len(color) == len(x):
+ # bar colors specified, need to expand to number of faces
+ for c in color:
+ facecolors.extend([c] * 6)
+ else:
+ # a single color specified, or face colors specified explicitly
+ facecolors = list(mcolors.to_rgba_array(color))
+ if len(facecolors) < len(x):
+ facecolors *= (6 * len(x))
+
+ if shade:
+ normals = self._generate_normals(polys)
+ sfacecolors = self._shade_colors(facecolors, normals)
+ else:
+ sfacecolors = facecolors
+
+ col = art3d.Poly3DCollection(polys,
+ zsort=zsort,
+ facecolor=sfacecolors,
+ *args, **kwargs)
+ self.add_collection(col)
+
+ self.auto_scale_xyz((minx, maxx), (miny, maxy), (minz, maxz), had_data)
+
+ return col
+
+ def set_title(self, label, fontdict=None, loc='center', **kwargs):
+ ret = super(Axes3D, self).set_title(label, fontdict=fontdict, loc=loc,
+ **kwargs)
+ (x, y) = self.title.get_position()
+ self.title.set_y(0.92 * y)
+ return ret
+ set_title.__doc__ = maxes.Axes.set_title.__doc__
+
+ def quiver(self, *args, **kwargs):
+ """
+ Plot a 3D field of arrows.
+
+ call signatures::
+
+ quiver(X, Y, Z, U, V, W, **kwargs)
+
+ Arguments:
+
+ *X*, *Y*, *Z*:
+ The x, y and z coordinates of the arrow locations (default is
+ tail of arrow; see *pivot* kwarg)
+
+ *U*, *V*, *W*:
+ The x, y and z components of the arrow vectors
+
+ The arguments could be array-like or scalars, so long as they
+ they can be broadcast together. The arguments can also be
+ masked arrays. If an element in any of argument is masked, then
+ that corresponding quiver element will not be plotted.
+
+ Keyword arguments:
+
+ *length*: [1.0 | float]
+ The length of each quiver, default to 1.0, the unit is
+ the same with the axes
+
+ *arrow_length_ratio*: [0.3 | float]
+ The ratio of the arrow head with respect to the quiver,
+ default to 0.3
+
+ *pivot*: [ 'tail' | 'middle' | 'tip' ]
+ The part of the arrow that is at the grid point; the arrow
+ rotates about this point, hence the name *pivot*.
+ Default is 'tail'
+
+ *normalize*: bool
+ When True, all of the arrows will be the same length. This
+ defaults to False, where the arrows will be different lengths
+ depending on the values of u,v,w.
+
+ Any additional keyword arguments are delegated to
+ :class:`~matplotlib.collections.LineCollection`
+
+ """
+ def calc_arrow(uvw, angle=15):
+ """
+ To calculate the arrow head. uvw should be a unit vector.
+ We normalize it here:
+ """
+ # get unit direction vector perpendicular to (u,v,w)
+ norm = np.linalg.norm(uvw[:2])
+ if norm > 0:
+ x = uvw[1] / norm
+ y = -uvw[0] / norm
+ else:
+ x, y = 0, 1
+
+ # compute the two arrowhead direction unit vectors
+ ra = math.radians(angle)
+ c = math.cos(ra)
+ s = math.sin(ra)
+
+ # construct the rotation matrices
+ Rpos = np.array([[c+(x**2)*(1-c), x*y*(1-c), y*s],
+ [y*x*(1-c), c+(y**2)*(1-c), -x*s],
+ [-y*s, x*s, c]])
+ # opposite rotation negates all the sin terms
+ Rneg = Rpos.copy()
+ Rneg[[0,1,2,2],[2,2,0,1]] = -Rneg[[0,1,2,2],[2,2,0,1]]
+
+ # multiply them to get the rotated vector
+ return Rpos.dot(uvw), Rneg.dot(uvw)
+
+ had_data = self.has_data()
+
+ # handle kwargs
+ # shaft length
+ length = kwargs.pop('length', 1)
+ # arrow length ratio to the shaft length
+ arrow_length_ratio = kwargs.pop('arrow_length_ratio', 0.3)
+ # pivot point
+ pivot = kwargs.pop('pivot', 'tail')
+ # normalize
+ normalize = kwargs.pop('normalize', False)
+
+ # handle args
+ argi = 6
+ if len(args) < argi:
+ raise ValueError('Wrong number of arguments. Expected %d got %d' %
+ (argi, len(args)))
+
+ # first 6 arguments are X, Y, Z, U, V, W
+ input_args = args[:argi]
+ # if any of the args are scalar, convert into list
+ input_args = [[k] if isinstance(k, (int, float)) else k
+ for k in input_args]
+
+ # extract the masks, if any
+ masks = [k.mask for k in input_args if isinstance(k, np.ma.MaskedArray)]
+ # broadcast to match the shape
+ bcast = np.broadcast_arrays(*(input_args + masks))
+ input_args = bcast[:argi]
+ masks = bcast[argi:]
+ if masks:
+ # combine the masks into one
+ mask = reduce(np.logical_or, masks)
+ # put mask on and compress
+ input_args = [np.ma.array(k, mask=mask).compressed()
+ for k in input_args]
+ else:
+ input_args = [k.flatten() for k in input_args]
+
+ if any(len(v) == 0 for v in input_args):
+ # No quivers, so just make an empty collection and return early
+ linec = art3d.Line3DCollection([], *args[argi:], **kwargs)
+ self.add_collection(linec)
+ return linec
+
+ # Following assertions must be true before proceeding
+ # must all be ndarray
+ assert all(isinstance(k, np.ndarray) for k in input_args)
+ # must all in same shape
+ assert len({k.shape for k in input_args}) == 1
+
+ shaft_dt = np.linspace(0, length, num=2)
+ arrow_dt = shaft_dt * arrow_length_ratio
+
+ if pivot == 'tail':
+ shaft_dt -= length
+ elif pivot == 'middle':
+ shaft_dt -= length/2.
+ elif pivot != 'tip':
+ raise ValueError('Invalid pivot argument: ' + str(pivot))
+
+ XYZ = np.column_stack(input_args[:3])
+ UVW = np.column_stack(input_args[3:argi]).astype(float)
+
+ # Normalize rows of UVW
+ # Note: with numpy 1.9+, could use np.linalg.norm(UVW, axis=1)
+ norm = np.sqrt(np.sum(UVW**2, axis=1))
+
+ # If any row of UVW is all zeros, don't make a quiver for it
+ mask = norm > 0
+ XYZ = XYZ[mask]
+ if normalize:
+ UVW = UVW[mask] / norm[mask].reshape((-1, 1))
+ else:
+ UVW = UVW[mask]
+
+ if len(XYZ) > 0:
+ # compute the shaft lines all at once with an outer product
+ shafts = (XYZ - np.multiply.outer(shaft_dt, UVW)).swapaxes(0, 1)
+ # compute head direction vectors, n heads by 2 sides by 3 dimensions
+ head_dirs = np.array([calc_arrow(d) for d in UVW])
+ # compute all head lines at once, starting from where the shaft ends
+ heads = shafts[:, :1] - np.multiply.outer(arrow_dt, head_dirs)
+ # stack left and right head lines together
+ heads.shape = (len(arrow_dt), -1, 3)
+ # transpose to get a list of lines
+ heads = heads.swapaxes(0, 1)
+
+ lines = list(shafts) + list(heads)
+ else:
+ lines = []
+
+ linec = art3d.Line3DCollection(lines, *args[argi:], **kwargs)
+ self.add_collection(linec)
+
+ self.auto_scale_xyz(XYZ[:, 0], XYZ[:, 1], XYZ[:, 2], had_data)
+
+ return linec
+
+ quiver3D = quiver
+
+ def voxels(self, *args, **kwargs):
+ """
+ ax.voxels([x, y, z,] /, filled, **kwargs)
+
+ Plot a set of filled voxels
+
+ All voxels are plotted as 1x1x1 cubes on the axis, with filled[0,0,0]
+ placed with its lower corner at the origin. Occluded faces are not
+ plotted.
+
+ Call signatures::
+
+ voxels(filled, facecolors=fc, edgecolors=ec, **kwargs)
+ voxels(x, y, z, filled, facecolors=fc, edgecolors=ec, **kwargs)
+
+ .. versionadded:: 2.1
+
+ Parameters
+ ----------
+ filled : 3D np.array of bool
+ A 3d array of values, with truthy values indicating which voxels
+ to fill
+
+ x, y, z : 3D np.array, optional
+ The coordinates of the corners of the voxels. This should broadcast
+ to a shape one larger in every dimension than the shape of `filled`.
+ These can be used to plot non-cubic voxels.
+
+ If not specified, defaults to increasing integers along each axis,
+ like those returned by :func:`~numpy.indices`.
+ As indicated by the ``/`` in the function signature, these arguments
+ can only be passed positionally.
+
+ facecolors, edgecolors : array_like, optional
+ The color to draw the faces and edges of the voxels. Can only be
+ passed as keyword arguments.
+ This parameter can be:
+
+ - A single color value, to color all voxels the same color. This
+ can be either a string, or a 1D rgb/rgba array
+ - ``None``, the default, to use a single color for the faces, and
+ the style default for the edges.
+ - A 3D ndarray of color names, with each item the color for the
+ corresponding voxel. The size must match the voxels.
+ - A 4D ndarray of rgb/rgba data, with the components along the
+ last axis.
+
+ **kwargs
+ Additional keyword arguments to pass onto
+ :func:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection`
+
+ Returns
+ -------
+ faces : dict
+ A dictionary indexed by coordinate, where ``faces[i,j,k]`` is a
+ `Poly3DCollection` of the faces drawn for the voxel
+ ``filled[i,j,k]``. If no faces were drawn for a given voxel, either
+ because it was not asked to be drawn, or it is fully occluded, then
+ ``(i,j,k) not in faces``.
+
+ Examples
+ --------
+ .. plot:: gallery/mplot3d/voxels.py
+ .. plot:: gallery/mplot3d/voxels_rgb.py
+ .. plot:: gallery/mplot3d/voxels_torus.py
+ .. plot:: gallery/mplot3d/voxels_numpy_logo.py
+ """
+
+ # work out which signature we should be using, and use it to parse
+ # the arguments. Name must be voxels for the correct error message
+ if len(args) >= 3:
+ # underscores indicate position only
+ def voxels(__x, __y, __z, filled, **kwargs):
+ return (__x, __y, __z), filled, kwargs
+ else:
+ def voxels(filled, **kwargs):
+ return None, filled, kwargs
+
+ xyz, filled, kwargs = voxels(*args, **kwargs)
+
+ # check dimensions
+ if filled.ndim != 3:
+ raise ValueError("Argument filled must be 3-dimensional")
+ size = np.array(filled.shape, dtype=np.intp)
+
+ # check xyz coordinates, which are one larger than the filled shape
+ coord_shape = tuple(size + 1)
+ if xyz is None:
+ x, y, z = np.indices(coord_shape)
+ else:
+ x, y, z = (_backports.broadcast_to(c, coord_shape) for c in xyz)
+
+ def _broadcast_color_arg(color, name):
+ if np.ndim(color) in (0, 1):
+ # single color, like "red" or [1, 0, 0]
+ return _backports.broadcast_to(
+ color, filled.shape + np.shape(color))
+ elif np.ndim(color) in (3, 4):
+ # 3D array of strings, or 4D array with last axis rgb
+ if np.shape(color)[:3] != filled.shape:
+ raise ValueError(
+ "When multidimensional, {} must match the shape of "
+ "filled".format(name))
+ return color
+ else:
+ raise ValueError("Invalid {} argument".format(name))
+
+ # intercept the facecolors, handling defaults and broacasting
+ facecolors = kwargs.pop('facecolors', None)
+ if facecolors is None:
+ facecolors = self._get_patches_for_fill.get_next_color()
+ facecolors = _broadcast_color_arg(facecolors, 'facecolors')
+
+ # broadcast but no default on edgecolors
+ edgecolors = kwargs.pop('edgecolors', None)
+ edgecolors = _broadcast_color_arg(edgecolors, 'edgecolors')
+
+ # always scale to the full array, even if the data is only in the center
+ self.auto_scale_xyz(x, y, z)
+
+ # points lying on corners of a square
+ square = np.array([
+ [0, 0, 0],
+ [0, 1, 0],
+ [1, 1, 0],
+ [1, 0, 0]
+ ], dtype=np.intp)
+
+ voxel_faces = defaultdict(list)
+
+ def permutation_matrices(n):
+ """ Generator of cyclic permutation matices """
+ mat = np.eye(n, dtype=np.intp)
+ for i in range(n):
+ yield mat
+ mat = np.roll(mat, 1, axis=0)
+
+ # iterate over each of the YZ, ZX, and XY orientations, finding faces to
+ # render
+ for permute in permutation_matrices(3):
+ # find the set of ranges to iterate over
+ pc, qc, rc = permute.T.dot(size)
+ pinds = np.arange(pc)
+ qinds = np.arange(qc)
+ rinds = np.arange(rc)
+
+ square_rot = square.dot(permute.T)
+
+ # iterate within the current plane
+ for p in pinds:
+ for q in qinds:
+ # iterate perpendicularly to the current plane, handling
+ # boundaries. We only draw faces between a voxel and an
+ # empty space, to avoid drawing internal faces.
+
+ # draw lower faces
+ p0 = permute.dot([p, q, 0])
+ i0 = tuple(p0)
+ if filled[i0]:
+ voxel_faces[i0].append(p0 + square_rot)
+
+ # draw middle faces
+ for r1, r2 in zip(rinds[:-1], rinds[1:]):
+ p1 = permute.dot([p, q, r1])
+ p2 = permute.dot([p, q, r2])
+
+ i1 = tuple(p1)
+ i2 = tuple(p2)
+
+ if filled[i1] and not filled[i2]:
+ voxel_faces[i1].append(p2 + square_rot)
+ elif not filled[i1] and filled[i2]:
+ voxel_faces[i2].append(p2 + square_rot)
+
+ # draw upper faces
+ pk = permute.dot([p, q, rc-1])
+ pk2 = permute.dot([p, q, rc])
+ ik = tuple(pk)
+ if filled[ik]:
+ voxel_faces[ik].append(pk2 + square_rot)
+
+ # iterate over the faces, and generate a Poly3DCollection for each voxel
+ polygons = {}
+ for coord, faces_inds in voxel_faces.items():
+ # convert indices into 3D positions
+ if xyz is None:
+ faces = faces_inds
+ else:
+ faces = []
+ for face_inds in faces_inds:
+ ind = face_inds[:, 0], face_inds[:, 1], face_inds[:, 2]
+ face = np.empty(face_inds.shape)
+ face[:, 0] = x[ind]
+ face[:, 1] = y[ind]
+ face[:, 2] = z[ind]
+ faces.append(face)
+
+ poly = art3d.Poly3DCollection(faces,
+ facecolors=facecolors[coord],
+ edgecolors=edgecolors[coord],
+ **kwargs
+ )
+ self.add_collection3d(poly)
+ polygons[coord] = poly
+
+ return polygons
+
+
+def get_test_data(delta=0.05):
+ '''
+ Return a tuple X, Y, Z with a test data set.
+ '''
+ x = y = np.arange(-3.0, 3.0, delta)
+ X, Y = np.meshgrid(x, y)
+
+ Z1 = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
+ Z2 = (np.exp(-(((X - 1) / 1.5)**2 + ((Y - 1) / 0.5)**2) / 2) /
+ (2 * np.pi * 0.5 * 1.5))
+ Z = Z2 - Z1
+
+ X = X * 10
+ Y = Y * 10
+ Z = Z * 500
+ return X, Y, Z
+
+
+########################################################
+# Register Axes3D as a 'projection' object available
+# for use just like any other axes
+########################################################
+import matplotlib.projections as proj
+proj.projection_registry.register(Axes3D)
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/axis3d.py b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/axis3d.py
new file mode 100644
index 0000000000..50b81df912
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/axis3d.py
@@ -0,0 +1,484 @@
+# axis3d.py, original mplot3d version by John Porter
+# Created: 23 Sep 2005
+# Parts rewritten by Reinier Heeres <reinier@heeres.eu>
+
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+
+import math
+import copy
+
+from matplotlib import lines as mlines, axis as maxis, patches as mpatches
+from matplotlib import rcParams
+from . import art3d
+from . import 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, **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(kwargs.get('rotate_label', None))
+
+ 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')
+
+ 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(math.degrees(math.atan2(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(math.degrees(math.atan2(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
diff --git a/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/proj3d.py b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/proj3d.py
new file mode 100644
index 0000000000..a084e7f36a
--- /dev/null
+++ b/contrib/python/matplotlib/py2/mpl_toolkits/mplot3d/proj3d.py
@@ -0,0 +1,203 @@
+# 3dproj.py
+#
+"""
+Various transforms used for by the 3D code
+"""
+from __future__ import (absolute_import, division, print_function,
+ unicode_literals)
+
+import six
+from six.moves import zip
+
+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)