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/axis_artist.py | |
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/axis_artist.py')
-rw-r--r-- | contrib/python/matplotlib/py2/mpl_toolkits/axisartist/axis_artist.py | 1527 |
1 files changed, 1527 insertions, 0 deletions
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) |