"""
Classes for the ticks and x and y axis.
"""
import datetime
import logging
import numpy as np
from matplotlib import rcParams
import matplotlib.artist as martist
import matplotlib.cbook as cbook
import matplotlib.font_manager as font_manager
import matplotlib.lines as mlines
import matplotlib.scale as mscale
import matplotlib.text as mtext
import matplotlib.ticker as mticker
import matplotlib.transforms as mtransforms
import matplotlib.units as munits
_log = logging.getLogger(__name__)
GRIDLINE_INTERPOLATION_STEPS = 180
# This list is being used for compatibility with Axes.grid, which
# allows all Line2D kwargs.
_line_AI = martist.ArtistInspector(mlines.Line2D)
_line_param_names = _line_AI.get_setters()
_line_param_aliases = [list(d)[0] for d in _line_AI.aliasd.values()]
_gridline_param_names = ['grid_' + name
for name in _line_param_names + _line_param_aliases]
class Tick(martist.Artist):
"""
Abstract base class for the axis ticks, grid lines and labels.
Ticks mark a position on an Axis. They contain two lines as markers and
two labels; one each for the bottom and top positions (in case of an
`.XAxis`) or for the left and right positions (in case of a `.YAxis`).
Attributes
----------
tick1line : `.Line2D`
The left/bottom tick marker.
tick2line : `.Line2D`
The right/top tick marker.
gridline : `.Line2D`
The grid line associated with the label position.
label1 : `.Text`
The left/bottom tick label.
label2 : `.Text`
The right/top tick label.
"""
def __init__(self, axes, loc, label,
size=None, # points
width=None,
color=None,
tickdir=None,
pad=None,
labelsize=None,
labelcolor=None,
zorder=None,
gridOn=None, # defaults to axes.grid depending on
# axes.grid.which
tick1On=True,
tick2On=True,
label1On=True,
label2On=False,
major=True,
labelrotation=0,
grid_color=None,
grid_linestyle=None,
grid_linewidth=None,
grid_alpha=None,
**kw # Other Line2D kwargs applied to gridlines.
):
"""
bbox is the Bound2D bounding box in display coords of the Axes
loc is the tick location in data coords
size is the tick size in points
"""
martist.Artist.__init__(self)
if gridOn is None:
if major and (rcParams['axes.grid.which'] in ('both', 'major')):
gridOn = rcParams['axes.grid']
elif (not major) and (rcParams['axes.grid.which']
in ('both', 'minor')):
gridOn = rcParams['axes.grid']
else:
gridOn = False
self.set_figure(axes.figure)
self.axes = axes
name = self.__name__.lower()
self._name = name
self._loc = loc
if size is None:
if major:
size = rcParams['%s.major.size' % name]
else:
size = rcParams['%s.minor.size' % name]
self._size = size
if width is None:
if major:
width = rcParams['%s.major.width' % name]
else:
width = rcParams['%s.minor.width' % name]
self._width = width
if color is None:
color = rcParams['%s.color' % name]
self._color = color
if pad is None:
if major:
pad = rcParams['%s.major.pad' % name]
else:
pad = rcParams['%s.minor.pad' % name]
self._base_pad = pad
if labelcolor is None:
labelcolor = rcParams['%s.color' % name]
self._labelcolor = labelcolor
if labelsize is None:
labelsize = rcParams['%s.labelsize' % name]
self._labelsize = labelsize
self._set_labelrotation(labelrotation)
if zorder is None:
if major:
zorder = mlines.Line2D.zorder + 0.01
else:
zorder = mlines.Line2D.zorder
self._zorder = zorder
self._grid_color = (rcParams['grid.color']
if grid_color is None else grid_color)
self._grid_linestyle = (rcParams['grid.linestyle']
if grid_linestyle is None else grid_linestyle)
self._grid_linewidth = (rcParams['grid.linewidth']
if grid_linewidth is None else grid_linewidth)
self._grid_alpha = (rcParams['grid.alpha']
if grid_alpha is None else grid_alpha)
self._grid_kw = {k[5:]: v for k, v in kw.items()}
self.apply_tickdir(tickdir)
self.tick1line = self._get_tick1line()
self.tick2line = self._get_tick2line()
self.gridline = self._get_gridline()
self.label1 = self._get_text1()
self.label2 = self._get_text2()
self.gridline.set_visible(gridOn)
self.tick1line.set_visible(tick1On)
self.tick2line.set_visible(tick2On)
self.label1.set_visible(label1On)
self.label2.set_visible(label2On)
self.update_position(loc)
for _old_name, _new_name in [
("gridOn", "gridline"),
("tick1On", "tick1line"),
("tick2On", "tick2line"),
("label1On", "label1"),
("label2On", "label2")]:
locals()[_old_name] = property(
cbook.deprecated(
"3.1",
name=_old_name,
alternative="Tick.{}.get_visible".format(_new_name))(
lambda self, _new_name=_new_name:
getattr(self, _new_name).get_visible()),
cbook.deprecated(
"3.1",
name=_old_name,
alternative="Tick.{}.set_visible".format(_new_name))(
lambda self, value, _new_name=_new_name:
getattr(self, _new_name).set_visible(value)))
del _old_name, _new_name
@property
@cbook.deprecated("3.1", alternative="Tick.label1", pending=True)
def label(self):
return self.label1
def _set_labelrotation(self, labelrotation):
if isinstance(labelrotation, str):
mode = labelrotation
angle = 0
elif isinstance(labelrotation, (tuple, list)):
mode, angle = labelrotation
else:
mode = 'default'
angle = labelrotation
cbook._check_in_list(['auto', 'default'], labelrotation=mode)
self._labelrotation = (mode, angle)
def apply_tickdir(self, tickdir):
"""Calculate self._pad and self._tickmarkers."""
def get_tickdir(self):
return self._tickdir
def get_tick_padding(self):
"""Get the length of the tick outside of the axes."""
padding = {
'in': 0.0,
'inout': 0.5,
'out': 1.0
}
return self._size * padding[self._tickdir]
def get_children(self):
children = [self.tick1line, self.tick2line,
self.gridline, self.label1, self.label2]
return children
def set_clip_path(self, clippath, transform=None):
# docstring inherited
martist.Artist.set_clip_path(self, clippath, transform)
self.gridline.set_clip_path(clippath, transform)
self.stale = True
def get_pad_pixels(self):
return self.figure.dpi * self._base_pad / 72
def contains(self, mouseevent):
"""
Test whether the mouse event occurred in the Tick marks.
This function always returns false. It is more useful to test if the
axis as a whole contains the mouse rather than the set of tick marks.
"""
if self._contains is not None:
return self._contains(self, mouseevent)
return False, {}
def set_pad(self, val):
"""
Set the tick label pad in points
Parameters
----------
val : float
"""
self._apply_params(pad=val)
self.stale = True
def get_pad(self):
'Get the value of the tick label pad in points'
return self._base_pad
def _get_text1(self):
'Get the default Text 1 instance'
pass
def _get_text2(self):
'Get the default Text 2 instance'
pass
def _get_tick1line(self):
'Get the default line2D instance for tick1'
pass
def _get_tick2line(self):
'Get the default line2D instance for tick2'
pass
def _get_gridline(self):
'Get the default grid Line2d instance for this tick'
pass
def get_loc(self):
'Return the tick location (data coords) as a scalar'
return self._loc
@martist.allow_rasterization
def draw(self, renderer):
if not self.get_visible():
self.stale = False
return
renderer.open_group(self.__name__)
for artist in [self.gridline, self.tick1line, self.tick2line,
self.label1, self.label2]:
artist.draw(renderer)
renderer.close_group(self.__name__)
self.stale = False
def set_label1(self, s):
"""
Set the label1 text.
Parameters
----------
s : str
"""
self.label1.set_text(s)
self.stale = True
set_label = set_label1
def set_label2(self, s):
"""
Set the label2 text.
Parameters
----------
s : str
"""
self.label2.set_text(s)
self.stale = True
def _set_artist_props(self, a):
a.set_figure(self.figure)
def get_view_interval(self):
'return the view Interval instance for the axis this tick is ticking'
raise NotImplementedError('Derived must override')
def _apply_params(self, **kw):
for name, target in [("gridOn", self.gridline),
("tick1On", self.tick1line),
("tick2On", self.tick2line),
("label1On", self.label1),
("label2On", self.label2)]:
if name in kw:
target.set_visible(kw.pop(name))
if any(k in kw for k in ['size', 'width', 'pad', 'tickdir']):
self._size = kw.pop('size', self._size)
# Width could be handled outside this block, but it is
# convenient to leave it here.
self._width = kw.pop('width', self._width)
self._base_pad = kw.pop('pad', self._base_pad)
# apply_tickdir uses _size and _base_pad to make _pad,
# and also makes _tickmarkers.
self.apply_tickdir(kw.pop('tickdir', self._tickdir))
self.tick1line.set_marker(self._tickmarkers[0])
self.tick2line.set_marker(self._tickmarkers[1])
for line in (self.tick1line, self.tick2line):
line.set_markersize(self._size)
line.set_markeredgewidth(self._width)
# _get_text1_transform uses _pad from apply_tickdir.
trans = self._get_text1_transform()[0]
self.label1.set_transform(trans)
trans = self._get_text2_transform()[0]
self.label2.set_transform(trans)
tick_kw = {k: v for k, v in kw.items() if k in ['color', 'zorder']}
self.tick1line.set(**tick_kw)
self.tick2line.set(**tick_kw)
for k, v in tick_kw.items():
setattr(self, '_' + k, v)
if 'labelrotation' in kw:
self._set_labelrotation(kw.pop('labelrotation'))
self.label1.set(rotation=self._labelrotation[1])
self.label2.set(rotation=self._labelrotation[1])
label_kw = {k[5:]: v for k, v in kw.items()
if k in ['labelsize', 'labelcolor']}
self.label1.set(**label_kw)
self.label2.set(**label_kw)
for k, v in label_kw.items():
# for labelsize the text objects covert str ('small')
# -> points. grab the integer from the `Text` object
# instead of saving the string representation
v = getattr(self.label1, 'get_' + k)()
setattr(self, '_label' + k, v)
grid_kw = {k[5:]: v for k, v in kw.items()
if k in _gridline_param_names}
self.gridline.set(**grid_kw)
for k, v in grid_kw.items():
setattr(self, '_grid_' + k, v)
def update_position(self, loc):
'Set the location of tick in data coords with scalar *loc*'
raise NotImplementedError('Derived must override')
def _get_text1_transform(self):
raise NotImplementedError('Derived must override')
def _get_text2_transform(self):
raise NotImplementedError('Derived must override')
class XTick(Tick):
"""
Contains all the Artists needed to make an x tick - the tick line,
the label text and the grid line
"""
__name__ = 'xtick'
def _get_text1_transform(self):
return self.axes.get_xaxis_text1_transform(self._pad)
def _get_text2_transform(self):
return self.axes.get_xaxis_text2_transform(self._pad)
def apply_tickdir(self, tickdir):
if tickdir is None:
tickdir = rcParams['%s.direction' % self._name]
self._tickdir = tickdir
if self._tickdir == 'in':
self._tickmarkers = (mlines.TICKUP, mlines.TICKDOWN)
elif self._tickdir == 'inout':
self._tickmarkers = ('|', '|')
else:
self._tickmarkers = (mlines.TICKDOWN, mlines.TICKUP)
self._pad = self._base_pad + self.get_tick_padding()
self.stale = True
def _get_text1(self):
'Get the default Text instance'
# the y loc is 3 points below the min of y axis
# get the affine as an a,b,c,d,tx,ty list
# x in data coords, y in axes coords
trans, vert, horiz = self._get_text1_transform()
t = mtext.Text(
x=0, y=0,
fontproperties=font_manager.FontProperties(size=self._labelsize),
color=self._labelcolor,
verticalalignment=vert,
horizontalalignment=horiz,
)
t.set_transform(trans)
self._set_artist_props(t)
return t
def _get_text2(self):
'Get the default Text 2 instance'
# x in data coords, y in axes coords
trans, vert, horiz = self._get_text2_transform()
t = mtext.Text(
x=0, y=1,
fontproperties=font_manager.FontProperties(size=self._labelsize),
color=self._labelcolor,
verticalalignment=vert,
horizontalalignment=horiz,
)
t.set_transform(trans)
self._set_artist_props(t)
return t
def _get_tick1line(self):
'Get the default line2D instance'
# x in data coords, y in axes coords
l = mlines.Line2D(xdata=(0,), ydata=(0,), color=self._color,
linestyle='None', marker=self._tickmarkers[0],
markersize=self._size,
markeredgewidth=self._width, zorder=self._zorder)
l.set_transform(self.axes.get_xaxis_transform(which='tick1'))
self._set_artist_props(l)
return l
def _get_tick2line(self):
'Get the default line2D instance'
# x in data coords, y in axes coords
l = mlines.Line2D(xdata=(0,), ydata=(1,),
color=self._color,
linestyle='None',
marker=self._tickmarkers[1],
markersize=self._size,
markeredgewidth=self._width,
zorder=self._zorder)
l.set_transform(self.axes.get_xaxis_transform(which='tick2'))
self._set_artist_props(l)
return l
def _get_gridline(self):
'Get the default line2D instance'
# x in data coords, y in axes coords
l = mlines.Line2D(xdata=(0.0, 0.0), ydata=(0, 1.0),
color=self._grid_color,
linestyle=self._grid_linestyle,
linewidth=self._grid_linewidth,
alpha=self._grid_alpha,
markersize=0,
**self._grid_kw)
l.set_transform(self.axes.get_xaxis_transform(which='grid'))
l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
self._set_artist_props(l)
return l
def update_position(self, loc):
"""Set the location of tick in data coords with scalar *loc*."""
self.tick1line.set_xdata((loc,))
self.tick2line.set_xdata((loc,))
self.gridline.set_xdata((loc,))
self.label1.set_x(loc)
self.label2.set_x(loc)
self._loc = loc
self.stale = True
def get_view_interval(self):
# docstring inherited
return self.axes.viewLim.intervalx
class YTick(Tick):
"""
Contains all the Artists needed to make a Y tick - the tick line,
the label text and the grid line
"""
__name__ = 'ytick'
def _get_text1_transform(self):
return self.axes.get_yaxis_text1_transform(self._pad)
def _get_text2_transform(self):
return self.axes.get_yaxis_text2_transform(self._pad)
def apply_tickdir(self, tickdir):
if tickdir is None:
tickdir = rcParams['%s.direction' % self._name]
self._tickdir = tickdir
if self._tickdir == 'in':
self._tickmarkers = (mlines.TICKRIGHT, mlines.TICKLEFT)
elif self._tickdir == 'inout':
self._tickmarkers = ('_', '_')
else:
self._tickmarkers = (mlines.TICKLEFT, mlines.TICKRIGHT)
self._pad = self._base_pad + self.get_tick_padding()
self.stale = True
# how far from the y axis line the right of the ticklabel are
def _get_text1(self):
'Get the default Text instance'
# x in axes coords, y in data coords
trans, vert, horiz = self._get_text1_transform()
t = mtext.Text(
x=0, y=0,
fontproperties=font_manager.FontProperties(size=self._labelsize),
color=self._labelcolor,
verticalalignment=vert,
horizontalalignment=horiz,
)
t.set_transform(trans)
self._set_artist_props(t)
return t
def _get_text2(self):
'Get the default Text instance'
# x in axes coords, y in data coords
trans, vert, horiz = self._get_text2_transform()
t = mtext.Text(
x=1, y=0,
fontproperties=font_manager.FontProperties(size=self._labelsize),
color=self._labelcolor,
verticalalignment=vert,
horizontalalignment=horiz,
)
t.set_transform(trans)
self._set_artist_props(t)
return t
def _get_tick1line(self):
'Get the default line2D instance'
# x in axes coords, y in data coords
l = mlines.Line2D((0,), (0,),
color=self._color,
marker=self._tickmarkers[0],
linestyle='None',
markersize=self._size,
markeredgewidth=self._width,
zorder=self._zorder)
l.set_transform(self.axes.get_yaxis_transform(which='tick1'))
self._set_artist_props(l)
return l
def _get_tick2line(self):
'Get the default line2D instance'
# x in axes coords, y in data coords
l = mlines.Line2D((1,), (0,),
color=self._color,
marker=self._tickmarkers[1],
linestyle='None',
markersize=self._size,
markeredgewidth=self._width,
zorder=self._zorder)
l.set_transform(self.axes.get_yaxis_transform(which='tick2'))
self._set_artist_props(l)
return l
def _get_gridline(self):
'Get the default line2D instance'
# x in axes coords, y in data coords
l = mlines.Line2D(xdata=(0, 1), ydata=(0, 0),
color=self._grid_color,
linestyle=self._grid_linestyle,
linewidth=self._grid_linewidth,
alpha=self._grid_alpha,
markersize=0,
**self._grid_kw)
l.set_transform(self.axes.get_yaxis_transform(which='grid'))
l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
self._set_artist_props(l)
return l
def update_position(self, loc):
"""Set the location of tick in data coords with scalar *loc*."""
self.tick1line.set_ydata((loc,))
self.tick2line.set_ydata((loc,))
self.gridline.set_ydata((loc,))
self.label1.set_y(loc)
self.label2.set_y(loc)
self._loc = loc
self.stale = True
def get_view_interval(self):
"""Return the Interval instance for this axis view limits."""
return self.axes.viewLim.intervaly
class Ticker(object):
"""
A container for the objects defining tick position and format.
Attributes
----------
locator : `matplotlib.ticker.Locator` subclass
Determines the positions of the ticks.
formatter : `matplotlib.ticker.Formatter` subclass
Determines the format of the tick labels.
"""
locator = None
formatter = None
class _LazyTickList(object):
"""
A descriptor for lazy instantiation of tick lists.
See comment above definition of the ``majorTicks`` and ``minorTicks``
attributes.
"""
def __init__(self, major):
self._major = major
def __get__(self, instance, cls):
if instance is None:
return self
else:
# instance._get_tick() can itself try to access the majorTicks
# attribute (e.g. in certain projection classes which override
# e.g. get_xaxis_text1_transform). In order to avoid infinite
# recursion, first set the majorTicks on the instance to an empty
# list, then create the tick and append it.
if self._major:
instance.majorTicks = []
tick = instance._get_tick(major=True)
instance.majorTicks.append(tick)
return instance.majorTicks
else:
instance.minorTicks = []
tick = instance._get_tick(major=False)
instance.minorTicks.append(tick)
return instance.minorTicks
class Axis(martist.Artist):
"""
Base class for `.XAxis` and `.YAxis`.
Attributes
----------
isDefault_label : bool
axes : `matplotlib.axes.Axes`
The `~.axes.Axes` to which the Axis belongs.
major : `matplotlib.axis.Ticker`
Determines the major tick positions and their label format.
minor : `matplotlib.axis.Ticker`
Determines the minor tick positions and their label format.
callbacks : `matplotlib.cbook.CallbackRegistry`
label : `.Text`
The axis label.
labelpad : float
The distance between the axis label and the tick labels.
Defaults to :rc:`axes.labelpad` = 4.
offsetText : `.Text`
A `.Text` object containing the data offset of the ticks (if any).
pickradius : float
The acceptance radius for containment tests. See also `.Axis.contains`.
majorTicks : list of `.Tick`
The major ticks.
minorTicks : list of `.Tick`
The minor ticks.
"""
OFFSETTEXTPAD = 3
def __str__(self):
return self.__class__.__name__ \
+ "(%f,%f)" % tuple(self.axes.transAxes.transform_point((0, 0)))
def __init__(self, axes, pickradius=15):
"""
Parameters
----------
axes : `matplotlib.axes.Axes`
The `~.axes.Axes` to which the created Axis belongs.
pickradius : float
The acceptance radius for containment tests. See also
`.Axis.contains`.
"""
martist.Artist.__init__(self)
self._remove_overlapping_locs = True
self.set_figure(axes.figure)
self.isDefault_label = True
self.axes = axes
self.major = Ticker()
self.minor = Ticker()
self.callbacks = cbook.CallbackRegistry()
self._autolabelpos = True
self._smart_bounds = False
self.label = self._get_label()
self.labelpad = rcParams['axes.labelpad']
self.offsetText = self._get_offset_text()
self.pickradius = pickradius
# Initialize here for testing; later add API
self._major_tick_kw = dict()
self._minor_tick_kw = dict()
self.cla()
self._set_scale('linear')
# During initialization, Axis objects often create ticks that are later
# unused; this turns out to be a very slow step. Instead, use a custom
# descriptor to make the tick lists lazy and instantiate them as needed.
majorTicks = _LazyTickList(major=True)
minorTicks = _LazyTickList(major=False)
def get_remove_overlapping_locs(self):
return self._remove_overlapping_locs
def set_remove_overlapping_locs(self, val):
self._remove_overlapping_locs = bool(val)
remove_overlapping_locs = property(
get_remove_overlapping_locs, set_remove_overlapping_locs,
doc=('If minor ticker locations that overlap with major '
'ticker locations should be trimmed.'))
def set_label_coords(self, x, y, transform=None):
"""
Set the coordinates of the label.
By default, the x coordinate of the y label is determined by the tick
label bounding boxes, but this can lead to poor alignment of multiple
ylabels if there are multiple axes. Ditto for the y coordinate of
the x label.
You can also specify the coordinate system of the label with
the transform. If None, the default coordinate system will be
the axes coordinate system (0,0) is (left,bottom), (0.5, 0.5)
is middle, etc
"""
self._autolabelpos = False
if transform is None:
transform = self.axes.transAxes
self.label.set_transform(transform)
self.label.set_position((x, y))
self.stale = True
def get_transform(self):
return self._scale.get_transform()
def get_scale(self):
return self._scale.name
def _set_scale(self, value, **kwargs):
self._scale = mscale.scale_factory(value, self, **kwargs)
self._scale.set_default_locators_and_formatters(self)
self.isDefault_majloc = True
self.isDefault_minloc = True
self.isDefault_majfmt = True
self.isDefault_minfmt = True
def limit_range_for_scale(self, vmin, vmax):
return self._scale.limit_range_for_scale(vmin, vmax, self.get_minpos())
def get_children(self):
children = [self.label, self.offsetText]
majorticks = self.get_major_ticks()
minorticks = self.get_minor_ticks()
children.extend(majorticks)
children.extend(minorticks)
return children
def cla(self):
'clear the current axis'
self.label.set_text('') # self.set_label_text would change isDefault_
self._set_scale('linear')
# Clear the callback registry for this axis, or it may "leak"
self.callbacks = cbook.CallbackRegistry()
# whether the grids are on
self._gridOnMajor = (rcParams['axes.grid'] and
rcParams['axes.grid.which'] in ('both', 'major'))
self._gridOnMinor = (rcParams['axes.grid'] and
rcParams['axes.grid.which'] in ('both', 'minor'))
self.reset_ticks()
self.converter = None
self.units = None
self.set_units(None)
self.stale = True
def reset_ticks(self):
"""
Re-initialize the major and minor Tick lists.
Each list starts with a single fresh Tick.
"""
# Restore the lazy tick lists.
try:
del self.majorTicks
except AttributeError:
pass
try:
del self.minorTicks
except AttributeError:
pass
try:
self.set_clip_path(self.axes.patch)
except AttributeError:
pass
def set_tick_params(self, which='major', reset=False, **kw):
"""
Set appearance parameters for ticks, ticklabels, and gridlines.
For documentation of keyword arguments, see
:meth:`matplotlib.axes.Axes.tick_params`.
"""
dicts = []
if which == 'major' or which == 'both':
dicts.append(self._major_tick_kw)
if which == 'minor' or which == 'both':
dicts.append(self._minor_tick_kw)
kwtrans = self._translate_tick_kw(kw)
# this stashes the parameter changes so any new ticks will
# automatically get them
for d in dicts:
if reset:
d.clear()
d.update(kwtrans)
if reset:
self.reset_ticks()
else:
# apply the new kwargs to the existing ticks
if which == 'major' or which == 'both':
for tick in self.majorTicks:
tick._apply_params(**kwtrans)
if which == 'minor' or which == 'both':
for tick in self.minorTicks:
tick._apply_params(**kwtrans)
# special-case label color to also apply to the offset
# text
if 'labelcolor' in kwtrans:
self.offsetText.set_color(kwtrans['labelcolor'])
self.stale = True
@staticmethod
def _translate_tick_kw(kw):
# The following lists may be moved to a more accessible location.
kwkeys = ['size', 'width', 'color', 'tickdir', 'pad',
'labelsize', 'labelcolor', 'zorder', 'gridOn',
'tick1On', 'tick2On', 'label1On', 'label2On',
'length', 'direction', 'left', 'bottom', 'right', 'top',
'labelleft', 'labelbottom', 'labelright', 'labeltop',
'labelrotation'] + _gridline_param_names
kwtrans = {}
if 'length' in kw:
kwtrans['size'] = kw.pop('length')
if 'direction' in kw:
kwtrans['tickdir'] = kw.pop('direction')
if 'rotation' in kw:
kwtrans['labelrotation'] = kw.pop('rotation')
if 'left' in kw:
kwtrans['tick1On'] = kw.pop('left')
if 'bottom' in kw:
kwtrans['tick1On'] = kw.pop('bottom')
if 'right' in kw:
kwtrans['tick2On'] = kw.pop('right')
if 'top' in kw:
kwtrans['tick2On'] = kw.pop('top')
if 'labelleft' in kw:
kwtrans['label1On'] = kw.pop('labelleft')
if 'labelbottom' in kw:
kwtrans['label1On'] = kw.pop('labelbottom')
if 'labelright' in kw:
kwtrans['label2On'] = kw.pop('labelright')
if 'labeltop' in kw:
kwtrans['label2On'] = kw.pop('labeltop')
if 'colors' in kw:
c = kw.pop('colors')
kwtrans['color'] = c
kwtrans['labelcolor'] = c
# Maybe move the checking up to the caller of this method.
for key in kw:
if key not in kwkeys:
raise ValueError(
"keyword %s is not recognized; valid keywords are %s"
% (key, kwkeys))
kwtrans.update(kw)
return kwtrans
def set_clip_path(self, clippath, transform=None):
martist.Artist.set_clip_path(self, clippath, transform)
for child in self.majorTicks + self.minorTicks:
child.set_clip_path(clippath, transform)
self.stale = True
def get_view_interval(self):
"""Return the Interval instance for this axis view limits."""
raise NotImplementedError('Derived must override')
def set_view_interval(self, vmin, vmax, ignore=False):
"""
Set the axis view limits. This method is for internal use; Matplotlib
users should typically use e.g. `~Axes.set_xlim` and `~Axes.set_ylim`.
If *ignore* is False (the default), this method will never reduce the
preexisting view limits, only expand them if *vmin* or *vmax* are not
within them. Moreover, the order of *vmin* and *vmax* does not matter;
the orientation of the axis will not change.
If *ignore* is True, the view limits will be set exactly to ``(vmin,
vmax)`` in that order.
"""
raise NotImplementedError('Derived must override')
def get_data_interval(self):
"""Return the Interval instance for this axis data limits."""
raise NotImplementedError('Derived must override')
def set_data_interval(self, vmin, vmax, ignore=False):
"""
Set the axis data limits. This method is for internal use.
If *ignore* is False (the default), this method will never reduce the
preexisting data limits, only expand them if *vmin* or *vmax* are not
within them. Moreover, the order of *vmin* and *vmax* does not matter;
the orientation of the axis will not change.
If *ignore* is True, the data limits will be set exactly to ``(vmin,
vmax)`` in that order.
"""
raise NotImplementedError('Derived must override')
def get_inverted(self):
"""
Return whether the axis is oriented in the "inverse" direction.
The "normal" direction is increasing to the right for the x-axis and to
the top for the y-axis; the "inverse" direction is increasing to the
left for the x-axis and to the bottom for the y-axis.
"""
low, high = self.get_view_interval()
return high < low
def set_inverted(self, inverted):
"""
Set whether the axis is oriented in the "inverse" direction.
The "normal" direction is increasing to the right for the x-axis and to
the top for the y-axis; the "inverse" direction is increasing to the
left for the x-axis and to the bottom for the y-axis.
"""
# Currently, must be implemented in subclasses using set_xlim/set_ylim
# rather than generically using set_view_interval, so that shared
# axes get updated as well.
raise NotImplementedError('Derived must override')
def set_default_intervals(self):
"""
Set the default limits for the axis data and view interval if they
have not been not mutated yet.
"""
# this is mainly in support of custom object plotting. For
# example, if someone passes in a datetime object, we do not
# know automagically how to set the default min/max of the
# data and view limits. The unit conversion AxisInfo
# interface provides a hook for custom types to register
# default limits through the AxisInfo.default_limits
# attribute, and the derived code below will check for that
# and use it if is available (else just use 0..1)
def _set_artist_props(self, a):
if a is None:
return
a.set_figure(self.figure)
@cbook.deprecated("3.1")
def iter_ticks(self):
"""
Yield ``(Tick, location, label)`` tuples for major and minor ticks.
"""
major_locs = self.get_majorticklocs()
major_labels = self.major.formatter.format_ticks(major_locs)
major_ticks = self.get_major_ticks(len(major_locs))
yield from zip(major_ticks, major_locs, major_labels)
minor_locs = self.get_minorticklocs()
minor_labels = self.minor.formatter.format_ticks(minor_locs)
minor_ticks = self.get_minor_ticks(len(minor_locs))
yield from zip(minor_ticks, minor_locs, minor_labels)
def get_ticklabel_extents(self, renderer):
"""
Get the extents of the tick labels on either side
of the axes.
"""
ticks_to_draw = self._update_ticks()
ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
renderer)
if len(ticklabelBoxes):
bbox = mtransforms.Bbox.union(ticklabelBoxes)
else:
bbox = mtransforms.Bbox.from_extents(0, 0, 0, 0)
if len(ticklabelBoxes2):
bbox2 = mtransforms.Bbox.union(ticklabelBoxes2)
else:
bbox2 = mtransforms.Bbox.from_extents(0, 0, 0, 0)
return bbox, bbox2
def set_smart_bounds(self, value):
"""Set the axis to have smart bounds."""
self._smart_bounds = value
self.stale = True
def get_smart_bounds(self):
"""Return whether the axis has smart bounds."""
return self._smart_bounds
def _update_ticks(self):
"""
Update ticks (position and labels) using the current data interval of
the axes. Return the list of ticks that will be drawn.
"""
major_locs = self.get_majorticklocs()
major_labels = self.major.formatter.format_ticks(major_locs)
major_ticks = self.get_major_ticks(len(major_locs))
self.major.formatter.set_locs(major_locs)
for tick, loc, label in zip(major_ticks, major_locs, major_labels):
tick.update_position(loc)
tick.set_label1(label)
tick.set_label2(label)
minor_locs = self.get_minorticklocs()
minor_labels = self.minor.formatter.format_ticks(minor_locs)
minor_ticks = self.get_minor_ticks(len(minor_locs))
self.minor.formatter.set_locs(minor_locs)
for tick, loc, label in zip(minor_ticks, minor_locs, minor_labels):
tick.update_position(loc)
tick.set_label1(label)
tick.set_label2(label)
ticks = [*major_ticks, *minor_ticks]
view_low, view_high = self.get_view_interval()
if view_low > view_high:
view_low, view_high = view_high, view_low
if self._smart_bounds and ticks:
# handle inverted limits
data_low, data_high = sorted(self.get_data_interval())
locs = np.sort([tick.get_loc() for tick in ticks])
if data_low = data_high]
if len(good_locs):
# first tick after or equal to last data point
ihigh = good_locs[0]
else:
# No ticks (why not?), take last tick
ihigh = locs[-1]
ticks = [tick for tick in ticks if ilow 0 zoom in, else zoom out."""
self.major.locator.zoom(direction)
def axis_date(self, tz=None):
"""
Sets up axis ticks and labels treating data along this axis as dates.
Parameters
----------
tz : tzinfo or str or None
The timezone used to create date labels.
"""
# By providing a sample datetime instance with the desired timezone,
# the registered converter can be selected, and the "units" attribute,
# which is the timezone, can be set.
if isinstance(tz, str):
import dateutil.tz
tz = dateutil.tz.gettz(tz)
self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz))
def get_tick_space(self):
"""Return the estimated number of ticks that can fit on the axis."""
# Must be overridden in the subclass
raise NotImplementedError()
def _get_ticks_position(self):
"""
Helper for `XAxis.get_ticks_position` and `YAxis.get_ticks_position`.
Check the visibility of tick1line, label1, tick2line, and label2 on
the first major and the first minor ticks, and return
- 1 if only tick1line and label1 are visible (which corresponds to
"bottom" for the x-axis and "left" for the y-axis);
- 2 if only tick2line and label2 are visible (which corresponds to
"top" for the x-axis and "right" for the y-axis);
- "default" if only tick1line, tick2line and label1 are visible;
- "unknown" otherwise.
"""
major = self.majorTicks[0]
minor = self.minorTicks[0]
if all(tick.tick1line.get_visible()
and not tick.tick2line.get_visible()
and tick.label1.get_visible()
and not tick.label2.get_visible()
for tick in [major, minor]):
return 1
elif all(tick.tick2line.get_visible()
and not tick.tick1line.get_visible()
and tick.label2.get_visible()
and not tick.label1.get_visible()
for tick in [major, minor]):
return 2
elif all(tick.tick1line.get_visible()
and tick.tick2line.get_visible()
and tick.label1.get_visible()
and not tick.label2.get_visible()
for tick in [major, minor]):
return "default"
else:
return "unknown"
def get_label_position(self):
"""
Return the label position (top or bottom)
"""
return self.label_position
def set_label_position(self, position):
"""
Set the label position (top or bottom)
Parameters
----------
position : {'top', 'bottom'}
"""
raise NotImplementedError()
def get_minpos(self):
raise NotImplementedError()
def _make_getset_interval(method_name, lim_name, attr_name):
"""
Helper to generate ``get_{data,view}_interval`` and
``set_{data,view}_interval`` implementations.
"""
def getter(self):
# docstring inherited.
return getattr(getattr(self.axes, lim_name), attr_name)
def setter(self, vmin, vmax, ignore=False):
# docstring inherited.
if ignore:
setattr(getattr(self.axes, lim_name), attr_name, (vmin, vmax))
else:
oldmin, oldmax = getter(self)
if oldmin < oldmax:
setter(self, min(vmin, vmax, oldmin), max(vmin, vmax, oldmax),
ignore=True)
else:
setter(self, max(vmin, vmax, oldmin), min(vmin, vmax, oldmax),
ignore=True)
self.stale = True
getter.__name__ = f"get_{method_name}_interval"
setter.__name__ = f"set_{method_name}_interval"
return getter, setter
class XAxis(Axis):
__name__ = 'xaxis'
axis_name = 'x'
def contains(self, mouseevent):
"""Test whether the mouse event occurred in the x axis.
"""
if self._contains is not None:
return self._contains(self, mouseevent)
x, y = mouseevent.x, mouseevent.y
try:
trans = self.axes.transAxes.inverted()
xaxes, yaxes = trans.transform_point((x, y))
except ValueError:
return False, {}
l, b = self.axes.transAxes.transform_point((0, 0))
r, t = self.axes.transAxes.transform_point((1, 1))
inaxis = 0