aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/matplotlib/py2/mpl_toolkits/axisartist
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/axisartist
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
downloadydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py2/mpl_toolkits/axisartist')
-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
13 files changed, 4527 insertions, 0 deletions
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)