diff options
author | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 14:39:34 +0300 |
---|---|---|
committer | shumkovnd <shumkovnd@yandex-team.com> | 2023-11-10 16:42:24 +0300 |
commit | 77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch) | |
tree | c51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/matplotlib/py2/mpl_toolkits/axisartist | |
parent | dd6d20cadb65582270ac23f4b3b14ae189704b9d (diff) | |
download | ydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz |
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/matplotlib/py2/mpl_toolkits/axisartist')
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) |