From 5e65bbe580192920226f99e15c77feed9eda944b Mon Sep 17 00:00:00 2001 From: Nelle Varoquaux Date: Fri, 28 Jun 2013 22:02:44 +0200 Subject: [PATCH 1/8] Splitted the axes module into smaller chunks --- lib/matplotlib/axes/__init__.py | 3 + lib/matplotlib/{axes.py => axes/_axes.py} | 3345 +-------------------- lib/matplotlib/axes/_base.py | 3130 +++++++++++++++++++ lib/matplotlib/axes/_subplots.py | 209 ++ lib/matplotlib/tests/test_axes.py | 4 +- setupext.py | 1 + 6 files changed, 3364 insertions(+), 3328 deletions(-) create mode 100644 lib/matplotlib/axes/__init__.py rename lib/matplotlib/{axes.py => axes/_axes.py} (65%) create mode 100644 lib/matplotlib/axes/_base.py create mode 100644 lib/matplotlib/axes/_subplots.py diff --git a/lib/matplotlib/axes/__init__.py b/lib/matplotlib/axes/__init__.py new file mode 100644 index 000000000000..ab0a39e72076 --- /dev/null +++ b/lib/matplotlib/axes/__init__.py @@ -0,0 +1,3 @@ +from matplotlib.axes._subplots import * +from matplotlib.axes._axes import * +from matplotlib.axes._axes import _string_to_bool diff --git a/lib/matplotlib/axes.py b/lib/matplotlib/axes/_axes.py similarity index 65% rename from lib/matplotlib/axes.py rename to lib/matplotlib/axes/_axes.py index 8eebb9608148..2937a68697d8 100644 --- a/lib/matplotlib/axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -1,7 +1,6 @@ from __future__ import division, print_function import math import warnings -from operator import itemgetter import itertools import numpy as np @@ -11,15 +10,12 @@ rcParams = matplotlib.rcParams import matplotlib.artist as martist -from matplotlib.artist import allow_rasterization -import matplotlib.axis as maxis import matplotlib.cbook as cbook import matplotlib.collections as mcoll import matplotlib.colors as mcolors import matplotlib.contour as mcontour import matplotlib.dates as _ # <-registers a date unit converter from matplotlib import docstring -import matplotlib.font_manager as font_manager import matplotlib.image as mimage import matplotlib.legend as mlegend import matplotlib.lines as mlines @@ -27,7 +23,6 @@ import matplotlib.mlab as mlab import matplotlib.path as mpath import matplotlib.patches as mpatches -import matplotlib.spines as mspines import matplotlib.quiver as mquiver import matplotlib.scale as mscale import matplotlib.stackplot as mstack @@ -38,3121 +33,27 @@ import matplotlib.transforms as mtransforms import matplotlib.tri as mtri from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer +from matplotlib.axes._base import _AxesBase, _string_to_bool iterable = cbook.iterable is_string_like = cbook.is_string_like is_sequence_of_strings = cbook.is_sequence_of_strings -def _string_to_bool(s): - if not is_string_like(s): - return s - if s == 'on': - return True - if s == 'off': - return False - raise ValueError("string argument must be either 'on' or 'off'") - - -def _process_plot_format(fmt): - """ - Process a MATLAB style color/line style format string. Return a - (*linestyle*, *color*) tuple as a result of the processing. Default - values are ('-', 'b'). Example format strings include: - - * 'ko': black circles - * '.b': blue dots - * 'r--': red dashed lines - - .. seealso:: - - :func:`~matplotlib.Line2D.lineStyles` and - :func:`~matplotlib.pyplot.colors` - for all possible styles and color format string. - """ - - linestyle = None - marker = None - color = None - - # Is fmt just a colorspec? - try: - color = mcolors.colorConverter.to_rgb(fmt) - - # We need to differentiate grayscale '1.0' from tri_down marker '1' - try: - fmtint = str(int(fmt)) - except ValueError: - return linestyle, marker, color # Yes - else: - if fmt != fmtint: - # user definitely doesn't want tri_down marker - return linestyle, marker, color # Yes - else: - # ignore converted color - color = None - except ValueError: - pass # No, not just a color. - - # handle the multi char special cases and strip them from the - # string - if fmt.find('--') >= 0: - linestyle = '--' - fmt = fmt.replace('--', '') - if fmt.find('-.') >= 0: - linestyle = '-.' - fmt = fmt.replace('-.', '') - if fmt.find(' ') >= 0: - linestyle = 'None' - fmt = fmt.replace(' ', '') - - chars = [c for c in fmt] - - for c in chars: - if c in mlines.lineStyles: - if linestyle is not None: - raise ValueError( - 'Illegal format string "%s"; two linestyle symbols' % fmt) - linestyle = c - elif c in mlines.lineMarkers: - if marker is not None: - raise ValueError( - 'Illegal format string "%s"; two marker symbols' % fmt) - marker = c - elif c in mcolors.colorConverter.colors: - if color is not None: - raise ValueError( - 'Illegal format string "%s"; two color symbols' % fmt) - color = c - else: - raise ValueError( - 'Unrecognized character %c in format string' % c) - - if linestyle is None and marker is None: - linestyle = rcParams['lines.linestyle'] - if linestyle is None: - linestyle = 'None' - if marker is None: - marker = 'None' - - return linestyle, marker, color - - -class _process_plot_var_args(object): - """ - Process variable length arguments to the plot command, so that - plot commands like the following are supported:: - - plot(t, s) - plot(t1, s1, t2, s2) - plot(t1, s1, 'ko', t2, s2) - plot(t1, s1, 'ko', t2, s2, 'r--', t3, e3) - - an arbitrary number of *x*, *y*, *fmt* are allowed - """ - def __init__(self, axes, command='plot'): - self.axes = axes - self.command = command - self.set_color_cycle() - - def __getstate__(self): - # note: it is not possible to pickle a itertools.cycle instance - return {'axes': self.axes, 'command': self.command} - - def __setstate__(self, state): - self.__dict__ = state.copy() - self.set_color_cycle() - - def set_color_cycle(self, clist=None): - if clist is None: - clist = rcParams['axes.color_cycle'] - self.color_cycle = itertools.cycle(clist) - - def __call__(self, *args, **kwargs): - - if self.axes.xaxis is not None and self.axes.yaxis is not None: - xunits = kwargs.pop('xunits', self.axes.xaxis.units) - - if self.axes.name == 'polar': - xunits = kwargs.pop('thetaunits', xunits) - - yunits = kwargs.pop('yunits', self.axes.yaxis.units) - - if self.axes.name == 'polar': - yunits = kwargs.pop('runits', yunits) - - if xunits != self.axes.xaxis.units: - self.axes.xaxis.set_units(xunits) - - if yunits != self.axes.yaxis.units: - self.axes.yaxis.set_units(yunits) - - ret = self._grab_next_args(*args, **kwargs) - return ret - - def set_lineprops(self, line, **kwargs): - assert self.command == 'plot', 'set_lineprops only works with "plot"' - for key, val in kwargs.items(): - funcName = "set_%s" % key - if not hasattr(line, funcName): - raise TypeError('There is no line property "%s"' % key) - func = getattr(line, funcName) - func(val) - - def set_patchprops(self, fill_poly, **kwargs): - assert self.command == 'fill', 'set_patchprops only works with "fill"' - for key, val in kwargs.items(): - funcName = "set_%s" % key - if not hasattr(fill_poly, funcName): - raise TypeError('There is no patch property "%s"' % key) - func = getattr(fill_poly, funcName) - func(val) - - def _xy_from_xy(self, x, y): - if self.axes.xaxis is not None and self.axes.yaxis is not None: - bx = self.axes.xaxis.update_units(x) - by = self.axes.yaxis.update_units(y) - - if self.command != 'plot': - # the Line2D class can handle unitized data, with - # support for post hoc unit changes etc. Other mpl - # artists, eg Polygon which _process_plot_var_args - # also serves on calls to fill, cannot. So this is a - # hack to say: if you are not "plot", which is - # creating Line2D, then convert the data now to - # floats. If you are plot, pass the raw data through - # to Line2D which will handle the conversion. So - # polygons will not support post hoc conversions of - # the unit type since they are not storing the orig - # data. Hopefully we can rationalize this at a later - # date - JDH - if bx: - x = self.axes.convert_xunits(x) - if by: - y = self.axes.convert_yunits(y) - - x = np.atleast_1d(x) # like asanyarray, but converts scalar to array - y = np.atleast_1d(y) - if x.shape[0] != y.shape[0]: - raise ValueError("x and y must have same first dimension") - if x.ndim > 2 or y.ndim > 2: - raise ValueError("x and y can be no greater than 2-D") - - if x.ndim == 1: - x = x[:, np.newaxis] - if y.ndim == 1: - y = y[:, np.newaxis] - return x, y - - def _makeline(self, x, y, kw, kwargs): - kw = kw.copy() # Don't modify the original kw. - if not 'color' in kw and not 'color' in kwargs.keys(): - kw['color'] = self.color_cycle.next() - # (can't use setdefault because it always evaluates - # its second argument) - seg = mlines.Line2D(x, y, - axes=self.axes, - **kw - ) - self.set_lineprops(seg, **kwargs) - return seg - - def _makefill(self, x, y, kw, kwargs): - try: - facecolor = kw['color'] - except KeyError: - facecolor = self.color_cycle.next() - seg = mpatches.Polygon(np.hstack((x[:, np.newaxis], - y[:, np.newaxis])), - facecolor=facecolor, - fill=True, - closed=kw['closed']) - self.set_patchprops(seg, **kwargs) - return seg - - def _plot_args(self, tup, kwargs): - ret = [] - if len(tup) > 1 and is_string_like(tup[-1]): - linestyle, marker, color = _process_plot_format(tup[-1]) - tup = tup[:-1] - elif len(tup) == 3: - raise ValueError('third arg must be a format string') - else: - linestyle, marker, color = None, None, None - kw = {} - for k, v in zip(('linestyle', 'marker', 'color'), - (linestyle, marker, color)): - if v is not None: - kw[k] = v - - y = np.atleast_1d(tup[-1]) - - if len(tup) == 2: - x = np.atleast_1d(tup[0]) - else: - x = np.arange(y.shape[0], dtype=float) - - x, y = self._xy_from_xy(x, y) - - if self.command == 'plot': - func = self._makeline - else: - kw['closed'] = kwargs.get('closed', True) - func = self._makefill - - ncx, ncy = x.shape[1], y.shape[1] - for j in xrange(max(ncx, ncy)): - seg = func(x[:, j % ncx], y[:, j % ncy], kw, kwargs) - ret.append(seg) - return ret - - def _grab_next_args(self, *args, **kwargs): - - remaining = args - while 1: - - if len(remaining) == 0: - return - if len(remaining) <= 3: - for seg in self._plot_args(remaining, kwargs): - yield seg - return - - if is_string_like(remaining[2]): - isplit = 3 - else: - isplit = 2 - - for seg in self._plot_args(remaining[:isplit], kwargs): - yield seg - remaining = remaining[isplit:] - - -class Axes(martist.Artist): - """ - The :class:`Axes` contains most of the figure elements: - :class:`~matplotlib.axis.Axis`, :class:`~matplotlib.axis.Tick`, - :class:`~matplotlib.lines.Line2D`, :class:`~matplotlib.text.Text`, - :class:`~matplotlib.patches.Polygon`, etc., and sets the - coordinate system. - - The :class:`Axes` instance supports callbacks through a callbacks - attribute which is a :class:`~matplotlib.cbook.CallbackRegistry` - instance. The events you can connect to are 'xlim_changed' and - 'ylim_changed' and the callback will be called with func(*ax*) - where *ax* is the :class:`Axes` instance. - """ - name = "rectilinear" - - _shared_x_axes = cbook.Grouper() - _shared_y_axes = cbook.Grouper() - - def __str__(self): - return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds) - - def __init__(self, fig, rect, - axisbg=None, # defaults to rc axes.facecolor - frameon=True, - sharex=None, # use Axes instance's xaxis info - sharey=None, # use Axes instance's yaxis info - label='', - xscale=None, - yscale=None, - **kwargs - ): - """ - Build an :class:`Axes` instance in - :class:`~matplotlib.figure.Figure` *fig* with - *rect=[left, bottom, width, height]* in - :class:`~matplotlib.figure.Figure` coordinates - - Optional keyword arguments: - - ================ ========================================= - Keyword Description - ================ ========================================= - *adjustable* [ 'box' | 'datalim' | 'box-forced'] - *alpha* float: the alpha transparency (can be None) - *anchor* [ 'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', - 'NW', 'W' ] - *aspect* [ 'auto' | 'equal' | aspect_ratio ] - *autoscale_on* [ *True* | *False* ] whether or not to - autoscale the *viewlim* - *axis_bgcolor* any matplotlib color, see - :func:`~matplotlib.pyplot.colors` - *axisbelow* draw the grids and ticks below the other - artists - *cursor_props* a (*float*, *color*) tuple - *figure* a :class:`~matplotlib.figure.Figure` - instance - *frame_on* a boolean - draw the axes frame - *label* the axes label - *navigate* [ *True* | *False* ] - *navigate_mode* [ 'PAN' | 'ZOOM' | None ] the navigation - toolbar button status - *position* [left, bottom, width, height] in - class:`~matplotlib.figure.Figure` coords - *sharex* an class:`~matplotlib.axes.Axes` instance - to share the x-axis with - *sharey* an class:`~matplotlib.axes.Axes` instance - to share the y-axis with - *title* the title string - *visible* [ *True* | *False* ] whether the axes is - visible - *xlabel* the xlabel - *xlim* (*xmin*, *xmax*) view limits - *xscale* [%(scale)s] - *xticklabels* sequence of strings - *xticks* sequence of floats - *ylabel* the ylabel strings - *ylim* (*ymin*, *ymax*) view limits - *yscale* [%(scale)s] - *yticklabels* sequence of strings - *yticks* sequence of floats - ================ ========================================= - """ % {'scale': ' | '.join( - [repr(x) for x in mscale.get_scale_names()])} - martist.Artist.__init__(self) - if isinstance(rect, mtransforms.Bbox): - self._position = rect - else: - self._position = mtransforms.Bbox.from_bounds(*rect) - self._originalPosition = self._position.frozen() - self.set_axes(self) - self.set_aspect('auto') - self._adjustable = 'box' - self.set_anchor('C') - self._sharex = sharex - self._sharey = sharey - if sharex is not None: - self._shared_x_axes.join(self, sharex) - if sharex._adjustable == 'box': - sharex._adjustable = 'datalim' - #warnings.warn( - # 'shared axes: "adjustable" is being changed to "datalim"') - self._adjustable = 'datalim' - if sharey is not None: - self._shared_y_axes.join(self, sharey) - if sharey._adjustable == 'box': - sharey._adjustable = 'datalim' - #warnings.warn( - # 'shared axes: "adjustable" is being changed to "datalim"') - self._adjustable = 'datalim' - self.set_label(label) - self.set_figure(fig) - - self.set_axes_locator(kwargs.get("axes_locator", None)) - - self.spines = self._gen_axes_spines() - - # this call may differ for non-sep axes, eg polar - self._init_axis() - - if axisbg is None: - axisbg = rcParams['axes.facecolor'] - self._axisbg = axisbg - self._frameon = frameon - self._axisbelow = rcParams['axes.axisbelow'] - - self._rasterization_zorder = None - - self._hold = rcParams['axes.hold'] - self._connected = {} # a dict from events to (id, func) - self.cla() - # funcs used to format x and y - fall back on major formatters - self.fmt_xdata = None - self.fmt_ydata = None - - self.set_cursor_props((1, 'k')) # set the cursor properties for axes - - self._cachedRenderer = None - self.set_navigate(True) - self.set_navigate_mode(None) - - if xscale: - self.set_xscale(xscale) - if yscale: - self.set_yscale(yscale) - - if len(kwargs): - martist.setp(self, **kwargs) - - if self.xaxis is not None: - self._xcid = self.xaxis.callbacks.connect('units finalize', - self.relim) - - if self.yaxis is not None: - self._ycid = self.yaxis.callbacks.connect('units finalize', - self.relim) - - def __setstate__(self, state): - self.__dict__ = state - # put the _remove_method back on all artists contained within the axes - for container_name in ['lines', 'collections', 'tables', 'patches', - 'texts', 'images']: - container = getattr(self, container_name) - for artist in container: - artist._remove_method = container.remove - - def get_window_extent(self, *args, **kwargs): - """ - get the axes bounding box in display space; *args* and - *kwargs* are empty - """ - return self.bbox - - def _init_axis(self): - "move this out of __init__ because non-separable axes don't use it" - self.xaxis = maxis.XAxis(self) - self.spines['bottom'].register_axis(self.xaxis) - self.spines['top'].register_axis(self.xaxis) - self.yaxis = maxis.YAxis(self) - self.spines['left'].register_axis(self.yaxis) - self.spines['right'].register_axis(self.yaxis) - self._update_transScale() - - def set_figure(self, fig): - """ - Set the class:`~matplotlib.axes.Axes` figure - - accepts a class:`~matplotlib.figure.Figure` instance - """ - martist.Artist.set_figure(self, fig) - - self.bbox = mtransforms.TransformedBbox(self._position, - fig.transFigure) - # these will be updated later as data is added - self.dataLim = mtransforms.Bbox.null() - self.viewLim = mtransforms.Bbox.unit() - self.transScale = mtransforms.TransformWrapper( - mtransforms.IdentityTransform()) - - self._set_lim_and_transforms() - - def _set_lim_and_transforms(self): - """ - set the *dataLim* and *viewLim* - :class:`~matplotlib.transforms.Bbox` attributes and the - *transScale*, *transData*, *transLimits* and *transAxes* - transformations. - - .. note:: - - This method is primarily used by rectilinear projections - of the :class:`~matplotlib.axes.Axes` class, and is meant - to be overridden by new kinds of projection axes that need - different transformations and limits. (See - :class:`~matplotlib.projections.polar.PolarAxes` for an - example. - - """ - self.transAxes = mtransforms.BboxTransformTo(self.bbox) - - # Transforms the x and y axis separately by a scale factor. - # It is assumed that this part will have non-linear components - # (e.g., for a log scale). - self.transScale = mtransforms.TransformWrapper( - mtransforms.IdentityTransform()) - - # An affine transformation on the data, generally to limit the - # range of the axes - self.transLimits = mtransforms.BboxTransformFrom( - mtransforms.TransformedBbox(self.viewLim, self.transScale)) - - # The parentheses are important for efficiency here -- they - # group the last two (which are usually affines) separately - # from the first (which, with log-scaling can be non-affine). - self.transData = self.transScale + (self.transLimits + self.transAxes) - - self._xaxis_transform = mtransforms.blended_transform_factory( - self.transData, self.transAxes) - self._yaxis_transform = mtransforms.blended_transform_factory( - self.transAxes, self.transData) - - def get_xaxis_transform(self, which='grid'): - """ - Get the transformation used for drawing x-axis labels, ticks - and gridlines. The x-direction is in data coordinates and the - y-direction is in axis coordinates. - - .. note:: - - This transformation is primarily used by the - :class:`~matplotlib.axis.Axis` class, and is meant to be - overridden by new kinds of projections that may need to - place axis elements in different locations. - - """ - if which == 'grid': - return self._xaxis_transform - elif which == 'tick1': - # for cartesian projection, this is bottom spine - return self.spines['bottom'].get_spine_transform() - elif which == 'tick2': - # for cartesian projection, this is top spine - return self.spines['top'].get_spine_transform() - else: - raise ValueError('unknown value for which') - - def get_xaxis_text1_transform(self, pad_points): - """ - Get the transformation used for drawing x-axis labels, which - will add the given amount of padding (in points) between the - axes and the label. The x-direction is in data coordinates - and the y-direction is in axis coordinates. Returns a - 3-tuple of the form:: - - (transform, valign, halign) - - where *valign* and *halign* are requested alignments for the - text. - - .. note:: - - This transformation is primarily used by the - :class:`~matplotlib.axis.Axis` class, and is meant to be - overridden by new kinds of projections that may need to - place axis elements in different locations. - - """ - return (self.get_xaxis_transform(which='tick1') + - mtransforms.ScaledTranslation(0, -1 * pad_points / 72.0, - self.figure.dpi_scale_trans), - "top", "center") - - def get_xaxis_text2_transform(self, pad_points): - """ - Get the transformation used for drawing the secondary x-axis - labels, which will add the given amount of padding (in points) - between the axes and the label. The x-direction is in data - coordinates and the y-direction is in axis coordinates. - Returns a 3-tuple of the form:: - - (transform, valign, halign) - - where *valign* and *halign* are requested alignments for the - text. - - .. note:: - - This transformation is primarily used by the - :class:`~matplotlib.axis.Axis` class, and is meant to be - overridden by new kinds of projections that may need to - place axis elements in different locations. - - """ - return (self.get_xaxis_transform(which='tick2') + - mtransforms.ScaledTranslation(0, pad_points / 72.0, - self.figure.dpi_scale_trans), - "bottom", "center") - - def get_yaxis_transform(self, which='grid'): - """ - Get the transformation used for drawing y-axis labels, ticks - and gridlines. The x-direction is in axis coordinates and the - y-direction is in data coordinates. - - .. note:: - - This transformation is primarily used by the - :class:`~matplotlib.axis.Axis` class, and is meant to be - overridden by new kinds of projections that may need to - place axis elements in different locations. - - """ - if which == 'grid': - return self._yaxis_transform - elif which == 'tick1': - # for cartesian projection, this is bottom spine - return self.spines['left'].get_spine_transform() - elif which == 'tick2': - # for cartesian projection, this is top spine - return self.spines['right'].get_spine_transform() - else: - raise ValueError('unknown value for which') - - def get_yaxis_text1_transform(self, pad_points): - """ - Get the transformation used for drawing y-axis labels, which - will add the given amount of padding (in points) between the - axes and the label. The x-direction is in axis coordinates - and the y-direction is in data coordinates. Returns a 3-tuple - of the form:: - - (transform, valign, halign) - - where *valign* and *halign* are requested alignments for the - text. - - .. note:: - - This transformation is primarily used by the - :class:`~matplotlib.axis.Axis` class, and is meant to be - overridden by new kinds of projections that may need to - place axis elements in different locations. - - """ - return (self.get_yaxis_transform(which='tick1') + - mtransforms.ScaledTranslation(-1 * pad_points / 72.0, 0, - self.figure.dpi_scale_trans), - "center", "right") - - def get_yaxis_text2_transform(self, pad_points): - """ - Get the transformation used for drawing the secondary y-axis - labels, which will add the given amount of padding (in points) - between the axes and the label. The x-direction is in axis - coordinates and the y-direction is in data coordinates. - Returns a 3-tuple of the form:: - - (transform, valign, halign) - - where *valign* and *halign* are requested alignments for the - text. - - .. note:: - - This transformation is primarily used by the - :class:`~matplotlib.axis.Axis` class, and is meant to be - overridden by new kinds of projections that may need to - place axis elements in different locations. - - """ - return (self.get_yaxis_transform(which='tick2') + - mtransforms.ScaledTranslation(pad_points / 72.0, 0, - self.figure.dpi_scale_trans), - "center", "left") - - def _update_transScale(self): - self.transScale.set( - mtransforms.blended_transform_factory( - self.xaxis.get_transform(), self.yaxis.get_transform())) - if hasattr(self, "lines"): - for line in self.lines: - try: - line._transformed_path.invalidate() - except AttributeError: - pass - - def get_position(self, original=False): - 'Return the a copy of the axes rectangle as a Bbox' - if original: - return self._originalPosition.frozen() - else: - return self._position.frozen() - - def set_position(self, pos, which='both'): - """ - Set the axes position with:: - - pos = [left, bottom, width, height] - - in relative 0,1 coords, or *pos* can be a - :class:`~matplotlib.transforms.Bbox` - - There are two position variables: one which is ultimately - used, but which may be modified by :meth:`apply_aspect`, and a - second which is the starting point for :meth:`apply_aspect`. - - - Optional keyword arguments: - *which* - - ========== ==================== - value description - ========== ==================== - 'active' to change the first - 'original' to change the second - 'both' to change both - ========== ==================== - - """ - if not isinstance(pos, mtransforms.BboxBase): - pos = mtransforms.Bbox.from_bounds(*pos) - if which in ('both', 'active'): - self._position.set(pos) - if which in ('both', 'original'): - self._originalPosition.set(pos) - - def reset_position(self): - """Make the original position the active position""" - pos = self.get_position(original=True) - self.set_position(pos, which='active') - - def set_axes_locator(self, locator): - """ - set axes_locator - - ACCEPT: a callable object which takes an axes instance and renderer and - returns a bbox. - """ - self._axes_locator = locator - - def get_axes_locator(self): - """ - return axes_locator - """ - return self._axes_locator - - def _set_artist_props(self, a): - """set the boilerplate props for artists added to axes""" - a.set_figure(self.figure) - if not a.is_transform_set(): - a.set_transform(self.transData) - - a.set_axes(self) - - 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. - - """ - return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0) - - def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'): - """ - Returns a dict whose keys are spine names and values are - Line2D or Patch instances. Each element is used to draw a - spine of the axes. - - In the standard axes, this is a single line segment, but in - other projections it may not be. - - .. note:: - - Intended to be overridden by new projection types. - - """ - return { - 'left': mspines.Spine.linear_spine(self, 'left'), - 'right': mspines.Spine.linear_spine(self, 'right'), - 'bottom': mspines.Spine.linear_spine(self, 'bottom'), - 'top': mspines.Spine.linear_spine(self, 'top'), } - - def cla(self): - """Clear the current axes.""" - # Note: this is called by Axes.__init__() - self.xaxis.cla() - self.yaxis.cla() - for name, spine in self.spines.iteritems(): - spine.cla() - - self.ignore_existing_data_limits = True - self.callbacks = cbook.CallbackRegistry() - - if self._sharex is not None: - # major and minor are class instances with - # locator and formatter attributes - self.xaxis.major = self._sharex.xaxis.major - self.xaxis.minor = self._sharex.xaxis.minor - x0, x1 = self._sharex.get_xlim() - self.set_xlim(x0, x1, emit=False, auto=None) - - # Save the current formatter/locator so we don't lose it - majf = self._sharex.xaxis.get_major_formatter() - minf = self._sharex.xaxis.get_minor_formatter() - majl = self._sharex.xaxis.get_major_locator() - minl = self._sharex.xaxis.get_minor_locator() - - # This overwrites the current formatter/locator - self.xaxis._set_scale(self._sharex.xaxis.get_scale()) - - # Reset the formatter/locator - self.xaxis.set_major_formatter(majf) - self.xaxis.set_minor_formatter(minf) - self.xaxis.set_major_locator(majl) - self.xaxis.set_minor_locator(minl) - else: - self.xaxis._set_scale('linear') - - if self._sharey is not None: - self.yaxis.major = self._sharey.yaxis.major - self.yaxis.minor = self._sharey.yaxis.minor - y0, y1 = self._sharey.get_ylim() - self.set_ylim(y0, y1, emit=False, auto=None) - - # Save the current formatter/locator so we don't lose it - majf = self._sharey.yaxis.get_major_formatter() - minf = self._sharey.yaxis.get_minor_formatter() - majl = self._sharey.yaxis.get_major_locator() - minl = self._sharey.yaxis.get_minor_locator() - - # This overwrites the current formatter/locator - self.yaxis._set_scale(self._sharey.yaxis.get_scale()) - - # Reset the formatter/locator - self.yaxis.set_major_formatter(majf) - self.yaxis.set_minor_formatter(minf) - self.yaxis.set_major_locator(majl) - self.yaxis.set_minor_locator(minl) - else: - self.yaxis._set_scale('linear') - - self._autoscaleXon = True - self._autoscaleYon = True - self._xmargin = rcParams['axes.xmargin'] - self._ymargin = rcParams['axes.ymargin'] - self._tight = False - self._update_transScale() # needed? - - self._get_lines = _process_plot_var_args(self) - self._get_patches_for_fill = _process_plot_var_args(self, 'fill') - - self._gridOn = rcParams['axes.grid'] - self.lines = [] - self.patches = [] - self.texts = [] - self.tables = [] - self.artists = [] - self.images = [] - self._current_image = None # strictly for pyplot via _sci, _gci - self.legend_ = None - self.collections = [] # collection.Collection instances - self.containers = [] - - self.grid(self._gridOn) - props = font_manager.FontProperties(size=rcParams['axes.titlesize']) - - self.titleOffsetTrans = mtransforms.ScaledTranslation( - 0.0, 5.0 / 72.0, self.figure.dpi_scale_trans) - self.title = mtext.Text( - x=0.5, y=1.0, text='', - fontproperties=props, - verticalalignment='baseline', - horizontalalignment='center', - ) - self._left_title = mtext.Text( - x=0.0, y=1.0, text='', - fontproperties=props, - verticalalignment='baseline', - horizontalalignment='left', ) - self._right_title = mtext.Text( - x=1.0, y=1.0, text='', - fontproperties=props, - verticalalignment='baseline', - horizontalalignment='right', - ) - - for _title in (self.title, self._left_title, self._right_title): - _title.set_transform(self.transAxes + self.titleOffsetTrans) - _title.set_clip_box(None) - self._set_artist_props(_title) - - # the patch draws the background of the axes. we want this to - # be below the other artists; the axesPatch name is - # deprecated. We use the frame to draw the edges so we are - # setting the edgecolor to None - self.patch = self.axesPatch = self._gen_axes_patch() - self.patch.set_figure(self.figure) - self.patch.set_facecolor(self._axisbg) - self.patch.set_edgecolor('None') - self.patch.set_linewidth(0) - self.patch.set_transform(self.transAxes) - - self.axison = True - - self.xaxis.set_clip_path(self.patch) - self.yaxis.set_clip_path(self.patch) - - self._shared_x_axes.clean() - self._shared_y_axes.clean() - - def clear(self): - """clear the axes""" - self.cla() - - def set_color_cycle(self, clist): - """ - Set the color cycle for any future plot commands on this Axes. - - *clist* is a list of mpl color specifiers. - """ - self._get_lines.set_color_cycle(clist) - self._get_patches_for_fill.set_color_cycle(clist) - - def ishold(self): - """return the HOLD status of the axes""" - return self._hold - - def hold(self, b=None): - """ - Call signature:: - - hold(b=None) - - Set the hold state. If *hold* is *None* (default), toggle the - *hold* state. Else set the *hold* state to boolean value *b*. - - Examples:: - - # toggle hold - hold() - - # turn hold on - hold(True) - - # turn hold off - hold(False) - - When hold is *True*, subsequent plot commands will be added to - the current axes. When hold is *False*, the current axes and - figure will be cleared on the next plot command - - """ - if b is None: - self._hold = not self._hold - else: - self._hold = b - - def get_aspect(self): - return self._aspect - - def set_aspect(self, aspect, adjustable=None, anchor=None): - """ - *aspect* - - ======== ================================================ - value description - ======== ================================================ - 'auto' automatic; fill position rectangle with data - 'normal' same as 'auto'; deprecated - 'equal' same scaling from data to plot units for x and y - num a circle will be stretched such that the height - is num times the width. aspect=1 is the same as - aspect='equal'. - ======== ================================================ - - *adjustable* - - ============ ===================================== - value description - ============ ===================================== - 'box' change physical size of axes - 'datalim' change xlim or ylim - 'box-forced' same as 'box', but axes can be shared - ============ ===================================== - - 'box' does not allow axes sharing, as this can cause - unintended side effect. For cases when sharing axes is - fine, use 'box-forced'. - - *anchor* - - ===== ===================== - value description - ===== ===================== - 'C' centered - 'SW' lower left corner - 'S' middle of bottom edge - 'SE' lower right corner - etc. - ===== ===================== - - .. deprecated:: 1.2 - the option 'normal' for aspect is deprecated. Use 'auto' instead. - """ - if aspect == 'normal': - cbook.warn_deprecated( - '1.2', name='normal', alternative='auto', obj_type='aspect') - self._aspect = 'auto' - - elif aspect in ('equal', 'auto'): - self._aspect = aspect - else: - self._aspect = float(aspect) # raise ValueError if necessary - - if adjustable is not None: - self.set_adjustable(adjustable) - if anchor is not None: - self.set_anchor(anchor) - - def get_adjustable(self): - return self._adjustable - - def set_adjustable(self, adjustable): - """ - ACCEPTS: [ 'box' | 'datalim' | 'box-forced'] - """ - if adjustable in ('box', 'datalim', 'box-forced'): - if self in self._shared_x_axes or self in self._shared_y_axes: - if adjustable == 'box': - raise ValueError( - 'adjustable must be "datalim" for shared axes') - self._adjustable = adjustable - else: - raise ValueError('argument must be "box", or "datalim"') - - def get_anchor(self): - return self._anchor - - def set_anchor(self, anchor): - """ - *anchor* - - ===== ============ - value description - ===== ============ - 'C' Center - 'SW' bottom left - 'S' bottom - 'SE' bottom right - 'E' right - 'NE' top right - 'N' top - 'NW' top left - 'W' left - ===== ============ - - """ - if anchor in mtransforms.Bbox.coefs.keys() or len(anchor) == 2: - self._anchor = anchor - else: - raise ValueError('argument must be among %s' % - ', '.join(mtransforms.Bbox.coefs.keys())) - - def get_data_ratio(self): - """ - Returns the aspect ratio of the raw data. - - This method is intended to be overridden by new projection - types. - """ - xmin, xmax = self.get_xbound() - ymin, ymax = self.get_ybound() - - xsize = max(math.fabs(xmax - xmin), 1e-30) - ysize = max(math.fabs(ymax - ymin), 1e-30) - - return ysize / xsize - - def get_data_ratio_log(self): - """ - Returns the aspect ratio of the raw data in log scale. - Will be used when both axis scales are in log. - """ - xmin, xmax = self.get_xbound() - ymin, ymax = self.get_ybound() - - xsize = max(math.fabs(math.log10(xmax) - math.log10(xmin)), 1e-30) - ysize = max(math.fabs(math.log10(ymax) - math.log10(ymin)), 1e-30) - - return ysize / xsize - - def apply_aspect(self, position=None): - """ - Use :meth:`_aspect` and :meth:`_adjustable` to modify the - axes box or the view limits. - """ - if position is None: - position = self.get_position(original=True) - - aspect = self.get_aspect() - - if self.name != 'polar': - xscale, yscale = self.get_xscale(), self.get_yscale() - if xscale == "linear" and yscale == "linear": - aspect_scale_mode = "linear" - elif xscale == "log" and yscale == "log": - aspect_scale_mode = "log" - elif ((xscale == "linear" and yscale == "log") or - (xscale == "log" and yscale == "linear")): - if aspect is not "auto": - warnings.warn( - 'aspect is not supported for Axes with xscale=%s, ' - 'yscale=%s' % (xscale, yscale)) - aspect = "auto" - else: # some custom projections have their own scales. - pass - else: - aspect_scale_mode = "linear" - - if aspect == 'auto': - self.set_position(position, which='active') - return - - if aspect == 'equal': - A = 1 - else: - A = aspect - - #Ensure at drawing time that any Axes involved in axis-sharing - # does not have its position changed. - if self in self._shared_x_axes or self in self._shared_y_axes: - if self._adjustable == 'box': - self._adjustable = 'datalim' - warnings.warn( - 'shared axes: "adjustable" is being changed to "datalim"') - - figW, figH = self.get_figure().get_size_inches() - fig_aspect = figH / figW - if self._adjustable in ['box', 'box-forced']: - if aspect_scale_mode == "log": - box_aspect = A * self.get_data_ratio_log() - else: - box_aspect = A * self.get_data_ratio() - pb = position.frozen() - pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) - self.set_position(pb1.anchored(self.get_anchor(), pb), 'active') - return - - # reset active to original in case it had been changed - # by prior use of 'box' - self.set_position(position, which='active') - - xmin, xmax = self.get_xbound() - ymin, ymax = self.get_ybound() - - if aspect_scale_mode == "log": - xmin, xmax = math.log10(xmin), math.log10(xmax) - ymin, ymax = math.log10(ymin), math.log10(ymax) - - xsize = max(math.fabs(xmax - xmin), 1e-30) - ysize = max(math.fabs(ymax - ymin), 1e-30) - - l, b, w, h = position.bounds - box_aspect = fig_aspect * (h / w) - data_ratio = box_aspect / A - - y_expander = (data_ratio * xsize / ysize - 1.0) - #print 'y_expander', y_expander - # If y_expander > 0, the dy/dx viewLim ratio needs to increase - if abs(y_expander) < 0.005: - #print 'good enough already' - return - - if aspect_scale_mode == "log": - dL = self.dataLim - dL_width = math.log10(dL.x1) - math.log10(dL.x0) - dL_height = math.log10(dL.y1) - math.log10(dL.y0) - xr = 1.05 * dL_width - yr = 1.05 * dL_height - else: - dL = self.dataLim - xr = 1.05 * dL.width - yr = 1.05 * dL.height - - xmarg = xsize - xr - ymarg = ysize - yr - Ysize = data_ratio * xsize - Xsize = ysize / data_ratio - Xmarg = Xsize - xr - Ymarg = Ysize - yr - xm = 0 # Setting these targets to, e.g., 0.05*xr does not seem to - # help. - ym = 0 - #print 'xmin, xmax, ymin, ymax', xmin, xmax, ymin, ymax - #print 'xsize, Xsize, ysize, Ysize', xsize, Xsize, ysize, Ysize - - changex = (self in self._shared_y_axes - and self not in self._shared_x_axes) - changey = (self in self._shared_x_axes - and self not in self._shared_y_axes) - if changex and changey: - warnings.warn("adjustable='datalim' cannot work with shared " - "x and y axes") - return - if changex: - adjust_y = False - else: - #print 'xmarg, ymarg, Xmarg, Ymarg', xmarg, ymarg, Xmarg, Ymarg - if xmarg > xm and ymarg > ym: - adjy = ((Ymarg > 0 and y_expander < 0) - or (Xmarg < 0 and y_expander > 0)) - else: - adjy = y_expander > 0 - #print 'y_expander, adjy', y_expander, adjy - adjust_y = changey or adjy # (Ymarg > xmarg) - if adjust_y: - yc = 0.5 * (ymin + ymax) - y0 = yc - Ysize / 2.0 - y1 = yc + Ysize / 2.0 - if aspect_scale_mode == "log": - self.set_ybound((10. ** y0, 10. ** y1)) - else: - self.set_ybound((y0, y1)) - #print 'New y0, y1:', y0, y1 - #print 'New ysize, ysize/xsize', y1-y0, (y1-y0)/xsize - else: - xc = 0.5 * (xmin + xmax) - x0 = xc - Xsize / 2.0 - x1 = xc + Xsize / 2.0 - if aspect_scale_mode == "log": - self.set_xbound((10. ** x0, 10. ** x1)) - else: - self.set_xbound((x0, x1)) - #print 'New x0, x1:', x0, x1 - #print 'New xsize, ysize/xsize', x1-x0, ysize/(x1-x0) - - def axis(self, *v, **kwargs): - """ - Convenience method for manipulating the x and y view limits - and the aspect ratio of the plot. For details, see - :func:`~matplotlib.pyplot.axis`. - - *kwargs* are passed on to :meth:`set_xlim` and - :meth:`set_ylim` - """ - if len(v) == 0 and len(kwargs) == 0: - xmin, xmax = self.get_xlim() - ymin, ymax = self.get_ylim() - return xmin, xmax, ymin, ymax - - if len(v) == 1 and is_string_like(v[0]): - s = v[0].lower() - if s == 'on': - self.set_axis_on() - elif s == 'off': - self.set_axis_off() - elif s in ('equal', 'tight', 'scaled', 'normal', 'auto', 'image'): - self.set_autoscale_on(True) - self.set_aspect('auto') - self.autoscale_view(tight=False) - # self.apply_aspect() - if s == 'equal': - self.set_aspect('equal', adjustable='datalim') - elif s == 'scaled': - self.set_aspect('equal', adjustable='box', anchor='C') - self.set_autoscale_on(False) # Req. by Mark Bakker - elif s == 'tight': - self.autoscale_view(tight=True) - self.set_autoscale_on(False) - elif s == 'image': - self.autoscale_view(tight=True) - self.set_autoscale_on(False) - self.set_aspect('equal', adjustable='box', anchor='C') - - else: - raise ValueError('Unrecognized string %s to axis; ' - 'try on or off' % s) - xmin, xmax = self.get_xlim() - ymin, ymax = self.get_ylim() - return xmin, xmax, ymin, ymax - - emit = kwargs.get('emit', True) - try: - v[0] - except IndexError: - xmin = kwargs.get('xmin', None) - xmax = kwargs.get('xmax', None) - auto = False # turn off autoscaling, unless... - if xmin is None and xmax is None: - auto = None # leave autoscaling state alone - xmin, xmax = self.set_xlim(xmin, xmax, emit=emit, auto=auto) - - ymin = kwargs.get('ymin', None) - ymax = kwargs.get('ymax', None) - auto = False # turn off autoscaling, unless... - if ymin is None and ymax is None: - auto = None # leave autoscaling state alone - ymin, ymax = self.set_ylim(ymin, ymax, emit=emit, auto=auto) - return xmin, xmax, ymin, ymax - - v = v[0] - if len(v) != 4: - raise ValueError('v must contain [xmin xmax ymin ymax]') - - self.set_xlim([v[0], v[1]], emit=emit, auto=False) - self.set_ylim([v[2], v[3]], emit=emit, auto=False) - - return v - - def get_legend(self): - """ - Return the legend.Legend instance, or None if no legend is defined - """ - return self.legend_ - - def get_images(self): - """return a list of Axes images contained by the Axes""" - return cbook.silent_list('AxesImage', self.images) - - def get_lines(self): - """Return a list of lines contained by the Axes""" - return cbook.silent_list('Line2D', self.lines) - - def get_xaxis(self): - """Return the XAxis instance""" - return self.xaxis - - def get_xgridlines(self): - """Get the x grid lines as a list of Line2D instances""" - return cbook.silent_list('Line2D xgridline', - self.xaxis.get_gridlines()) - - def get_xticklines(self): - """Get the xtick lines as a list of Line2D instances""" - return cbook.silent_list('Text xtickline', - self.xaxis.get_ticklines()) - - def get_yaxis(self): - """Return the YAxis instance""" - return self.yaxis - - def get_ygridlines(self): - """Get the y grid lines as a list of Line2D instances""" - return cbook.silent_list('Line2D ygridline', - self.yaxis.get_gridlines()) - - def get_yticklines(self): - """Get the ytick lines as a list of Line2D instances""" - return cbook.silent_list('Line2D ytickline', - self.yaxis.get_ticklines()) - - #### Adding and tracking artists - - def _sci(self, im): - """ - helper for :func:`~matplotlib.pyplot.sci`; - do not use elsewhere. - """ - if isinstance(im, matplotlib.contour.ContourSet): - if im.collections[0] not in self.collections: - raise ValueError( - "ContourSet must be in current Axes") - elif im not in self.images and im not in self.collections: - raise ValueError( - "Argument must be an image, collection, or ContourSet in " - "this Axes") - self._current_image = im - - def _gci(self): - """ - Helper for :func:`~matplotlib.pyplot.gci`; - do not use elsewhere. - """ - return self._current_image - - def has_data(self): - """ - Return *True* if any artists have been added to axes. - - This should not be used to determine whether the *dataLim* - need to be updated, and may not actually be useful for - anything. - """ - return ( - len(self.collections) + - len(self.images) + - len(self.lines) + - len(self.patches)) > 0 - - def add_artist(self, a): - """ - Add any :class:`~matplotlib.artist.Artist` to the axes. - - Returns the artist. - """ - a.set_axes(self) - self.artists.append(a) - self._set_artist_props(a) - a.set_clip_path(self.patch) - a._remove_method = lambda h: self.artists.remove(h) - return a - - def add_collection(self, collection, autolim=True): - """ - Add a :class:`~matplotlib.collections.Collection` instance - to the axes. - - Returns the collection. - """ - label = collection.get_label() - if not label: - collection.set_label('_collection%d' % len(self.collections)) - self.collections.append(collection) - self._set_artist_props(collection) - - if collection.get_clip_path() is None: - collection.set_clip_path(self.patch) - - if (autolim and - collection._paths is not None and - len(collection._paths) and - len(collection._offsets)): - self.update_datalim(collection.get_datalim(self.transData)) - - collection._remove_method = lambda h: self.collections.remove(h) - return collection - - def add_line(self, line): - """ - Add a :class:`~matplotlib.lines.Line2D` to the list of plot - lines - - Returns the line. - """ - self._set_artist_props(line) - if line.get_clip_path() is None: - line.set_clip_path(self.patch) - - self._update_line_limits(line) - if not line.get_label(): - line.set_label('_line%d' % len(self.lines)) - self.lines.append(line) - line._remove_method = lambda h: self.lines.remove(h) - return line - - def _update_line_limits(self, line): - """ - Figures out the data limit of the given line, updating self.dataLim. - """ - path = line.get_path() - if path.vertices.size == 0: - return - - line_trans = line.get_transform() - - if line_trans == self.transData: - data_path = path - - elif any(line_trans.contains_branch_seperately(self.transData)): - # identify the transform to go from line's coordinates - # to data coordinates - trans_to_data = line_trans - self.transData - - # if transData is affine we can use the cached non-affine component - # of line's path. (since the non-affine part of line_trans is - # entirely encapsulated in trans_to_data). - if self.transData.is_affine: - line_trans_path = line._get_transformed_path() - na_path, _ = line_trans_path.get_transformed_path_and_affine() - data_path = trans_to_data.transform_path_affine(na_path) - else: - data_path = trans_to_data.transform_path(path) - else: - # for backwards compatibility we update the dataLim with the - # coordinate range of the given path, even though the coordinate - # systems are completely different. This may occur in situations - # such as when ax.transAxes is passed through for absolute - # positioning. - data_path = path - - if data_path.vertices.size > 0: - updatex, updatey = line_trans.contains_branch_seperately( - self.transData - ) - self.dataLim.update_from_path(data_path, - self.ignore_existing_data_limits, - updatex=updatex, - updatey=updatey) - self.ignore_existing_data_limits = False - - def add_patch(self, p): - """ - Add a :class:`~matplotlib.patches.Patch` *p* to the list of - axes patches; the clipbox will be set to the Axes clipping - box. If the transform is not set, it will be set to - :attr:`transData`. - - Returns the patch. - """ - - self._set_artist_props(p) - if p.get_clip_path() is None: - p.set_clip_path(self.patch) - self._update_patch_limits(p) - self.patches.append(p) - p._remove_method = lambda h: self.patches.remove(h) - return p - - def _update_patch_limits(self, patch): - """update the data limits for patch *p*""" - # hist can add zero height Rectangles, which is useful to keep - # the bins, counts and patches lined up, but it throws off log - # scaling. We'll ignore rects with zero height or width in - # the auto-scaling - - # cannot check for '==0' since unitized data may not compare to zero - if (isinstance(patch, mpatches.Rectangle) and - ((not patch.get_width()) or (not patch.get_height()))): - return - vertices = patch.get_path().vertices - if vertices.size > 0: - xys = patch.get_patch_transform().transform(vertices) - if patch.get_data_transform() != self.transData: - patch_to_data = (patch.get_data_transform() - - self.transData) - xys = patch_to_data.transform(xys) - - updatex, updatey = patch.get_transform().\ - contains_branch_seperately(self.transData) - self.update_datalim(xys, updatex=updatex, - updatey=updatey) - - def add_table(self, tab): - """ - Add a :class:`~matplotlib.tables.Table` instance to the - list of axes tables - - Returns the table. - """ - self._set_artist_props(tab) - self.tables.append(tab) - tab.set_clip_path(self.patch) - tab._remove_method = lambda h: self.tables.remove(h) - return tab - - def add_container(self, container): - """ - Add a :class:`~matplotlib.container.Container` instance - to the axes. - - Returns the collection. - """ - label = container.get_label() - if not label: - container.set_label('_container%d' % len(self.containers)) - self.containers.append(container) - container.set_remove_method(lambda h: self.containers.remove(h)) - return container - - def relim(self): - """ - Recompute the data limits based on current artists. - - At present, :class:`~matplotlib.collections.Collection` - instances are not supported. - """ - # Collections are deliberately not supported (yet); see - # the TODO note in artists.py. - self.dataLim.ignore(True) - self.dataLim.set_points(mtransforms.Bbox.null().get_points()) - self.ignore_existing_data_limits = True - - for line in self.lines: - self._update_line_limits(line) - - for p in self.patches: - self._update_patch_limits(p) - - - def update_datalim(self, xys, updatex=True, updatey=True): - """ - Update the data lim bbox with seq of xy tups or equiv. 2-D array - """ - # if no data is set currently, the bbox will ignore its - # limits and set the bound to be the bounds of the xydata. - # Otherwise, it will compute the bounds of it's current data - # and the data in xydata - - if iterable(xys) and not len(xys): - return - if not ma.isMaskedArray(xys): - xys = np.asarray(xys) - self.dataLim.update_from_data_xy(xys, self.ignore_existing_data_limits, - updatex=updatex, updatey=updatey) - self.ignore_existing_data_limits = False - - def update_datalim_numerix(self, x, y): - """ - Update the data lim bbox with seq of xy tups - """ - # if no data is set currently, the bbox will ignore it's - # limits and set the bound to be the bounds of the xydata. - # Otherwise, it will compute the bounds of it's current data - # and the data in xydata - if iterable(x) and not len(x): - return - self.dataLim.update_from_data(x, y, self.ignore_existing_data_limits) - self.ignore_existing_data_limits = False - - def update_datalim_bounds(self, bounds): - """ - Update the datalim to include the given - :class:`~matplotlib.transforms.Bbox` *bounds* - """ - self.dataLim.set(mtransforms.Bbox.union([self.dataLim, bounds])) - - def _process_unit_info(self, xdata=None, ydata=None, kwargs=None): - """Look for unit *kwargs* and update the axis instances as necessary""" - - if self.xaxis is None or self.yaxis is None: - return - - #print 'processing', self.get_geometry() - if xdata is not None: - # we only need to update if there is nothing set yet. - if not self.xaxis.have_units(): - self.xaxis.update_units(xdata) - #print '\tset from xdata', self.xaxis.units - - if ydata is not None: - # we only need to update if there is nothing set yet. - if not self.yaxis.have_units(): - self.yaxis.update_units(ydata) - #print '\tset from ydata', self.yaxis.units - - # process kwargs 2nd since these will override default units - if kwargs is not None: - xunits = kwargs.pop('xunits', self.xaxis.units) - if self.name == 'polar': - xunits = kwargs.pop('thetaunits', xunits) - if xunits != self.xaxis.units: - #print '\tkw setting xunits', xunits - self.xaxis.set_units(xunits) - # If the units being set imply a different converter, - # we need to update. - if xdata is not None: - self.xaxis.update_units(xdata) - - yunits = kwargs.pop('yunits', self.yaxis.units) - if self.name == 'polar': - yunits = kwargs.pop('runits', yunits) - if yunits != self.yaxis.units: - #print '\tkw setting yunits', yunits - self.yaxis.set_units(yunits) - # If the units being set imply a different converter, - # we need to update. - if ydata is not None: - self.yaxis.update_units(ydata) - - def in_axes(self, mouseevent): - """ - Return *True* if the given *mouseevent* (in display coords) - is in the Axes - """ - return self.patch.contains(mouseevent)[0] - - def get_autoscale_on(self): - """ - Get whether autoscaling is applied for both axes on plot commands - """ - return self._autoscaleXon and self._autoscaleYon - - def get_autoscalex_on(self): - """ - Get whether autoscaling for the x-axis is applied on plot commands - """ - return self._autoscaleXon - - def get_autoscaley_on(self): - """ - Get whether autoscaling for the y-axis is applied on plot commands - """ - return self._autoscaleYon - - def set_autoscale_on(self, b): - """ - Set whether autoscaling is applied on plot commands - - accepts: [ *True* | *False* ] - """ - self._autoscaleXon = b - self._autoscaleYon = b - - def set_autoscalex_on(self, b): - """ - Set whether autoscaling for the x-axis is applied on plot commands - - accepts: [ *True* | *False* ] - """ - self._autoscaleXon = b - - def set_autoscaley_on(self, b): - """ - Set whether autoscaling for the y-axis is applied on plot commands - - accepts: [ *True* | *False* ] - """ - self._autoscaleYon = b - - def set_xmargin(self, m): - """ - Set padding of X data limits prior to autoscaling. - - *m* times the data interval will be added to each - end of that interval before it is used in autoscaling. - - accepts: float in range 0 to 1 - """ - if m < 0 or m > 1: - raise ValueError("margin must be in range 0 to 1") - self._xmargin = m - - def set_ymargin(self, m): - """ - Set padding of Y data limits prior to autoscaling. - - *m* times the data interval will be added to each - end of that interval before it is used in autoscaling. - - accepts: float in range 0 to 1 - """ - if m < 0 or m > 1: - raise ValueError("margin must be in range 0 to 1") - self._ymargin = m - - def margins(self, *args, **kw): - """ - Set or retrieve autoscaling margins. - - signatures:: - - margins() - - returns xmargin, ymargin - - :: - - margins(margin) - - margins(xmargin, ymargin) - - margins(x=xmargin, y=ymargin) - - margins(..., tight=False) - - All three forms above set the xmargin and ymargin parameters. - All keyword parameters are optional. A single argument - specifies both xmargin and ymargin. The *tight* parameter - is passed to :meth:`autoscale_view`, which is executed after - a margin is changed; the default here is *True*, on the - assumption that when margins are specified, no additional - padding to match tick marks is usually desired. Setting - *tight* to *None* will preserve the previous setting. - - Specifying any margin changes only the autoscaling; for example, - if *xmargin* is not None, then *xmargin* times the X data - interval will be added to each end of that interval before - it is used in autoscaling. - - """ - if not args and not kw: - return self._xmargin, self._ymargin - - tight = kw.pop('tight', True) - mx = kw.pop('x', None) - my = kw.pop('y', None) - if len(args) == 1: - mx = my = args[0] - elif len(args) == 2: - mx, my = args - else: - raise ValueError("more than two arguments were supplied") - if mx is not None: - self.set_xmargin(mx) - if my is not None: - self.set_ymargin(my) - - scalex = (mx is not None) - scaley = (my is not None) - - self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley) - - def set_rasterization_zorder(self, z): - """ - Set zorder value below which artists will be rasterized. Set - to `None` to disable rasterizing of artists below a particular - zorder. - """ - self._rasterization_zorder = z - - def get_rasterization_zorder(self): - """ - Get zorder value below which artists will be rasterized - """ - return self._rasterization_zorder - - def autoscale(self, enable=True, axis='both', tight=None): - """ - Autoscale the axis view to the data (toggle). - - Convenience method for simple axis view autoscaling. - It turns autoscaling on or off, and then, - if autoscaling for either axis is on, it performs - the autoscaling on the specified axis or axes. - - *enable*: [True | False | None] - True (default) turns autoscaling on, False turns it off. - None leaves the autoscaling state unchanged. - - *axis*: ['x' | 'y' | 'both'] - which axis to operate on; default is 'both' - - *tight*: [True | False | None] - If True, set view limits to data limits; - if False, let the locator and margins expand the view limits; - if None, use tight scaling if the only artist is an image, - otherwise treat *tight* as False. - The *tight* setting is retained for future autoscaling - until it is explicitly changed. - - - Returns None. - """ - if enable is None: - scalex = True - scaley = True - else: - scalex = False - scaley = False - if axis in ['x', 'both']: - self._autoscaleXon = bool(enable) - scalex = self._autoscaleXon - if axis in ['y', 'both']: - self._autoscaleYon = bool(enable) - scaley = self._autoscaleYon - self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley) - - def autoscale_view(self, tight=None, scalex=True, scaley=True): - """ - Autoscale the view limits using the data limits. You can - selectively autoscale only a single axis, eg, the xaxis by - setting *scaley* to *False*. The autoscaling preserves any - axis direction reversal that has already been done. - - The data limits are not updated automatically when artist data are - changed after the artist has been added to an Axes instance. In that - case, use :meth:`matplotlib.axes.Axes.relim` prior to calling - autoscale_view. - """ - if tight is None: - # if image data only just use the datalim - _tight = self._tight or (len(self.images) > 0 and - len(self.lines) == 0 and - len(self.patches) == 0) - else: - _tight = self._tight = bool(tight) - - if scalex and self._autoscaleXon: - xshared = self._shared_x_axes.get_siblings(self) - dl = [ax.dataLim for ax in xshared] - bb = mtransforms.BboxBase.union(dl) - x0, x1 = bb.intervalx - xlocator = self.xaxis.get_major_locator() - try: - # e.g., DateLocator has its own nonsingular() - x0, x1 = xlocator.nonsingular(x0, x1) - except AttributeError: - # Default nonsingular for, e.g., MaxNLocator - x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False, - expander=0.05) - if self._xmargin > 0: - delta = (x1 - x0) * self._xmargin - x0 -= delta - x1 += delta - if not _tight: - x0, x1 = xlocator.view_limits(x0, x1) - self.set_xbound(x0, x1) - - if scaley and self._autoscaleYon: - yshared = self._shared_y_axes.get_siblings(self) - dl = [ax.dataLim for ax in yshared] - bb = mtransforms.BboxBase.union(dl) - y0, y1 = bb.intervaly - ylocator = self.yaxis.get_major_locator() - try: - y0, y1 = ylocator.nonsingular(y0, y1) - except AttributeError: - y0, y1 = mtransforms.nonsingular(y0, y1, increasing=False, - expander=0.05) - if self._ymargin > 0: - delta = (y1 - y0) * self._ymargin - y0 -= delta - y1 += delta - if not _tight: - y0, y1 = ylocator.view_limits(y0, y1) - self.set_ybound(y0, y1) - - #### Drawing - - @allow_rasterization - def draw(self, renderer=None, inframe=False): - """Draw everything (plot lines, axes, labels)""" - if renderer is None: - renderer = self._cachedRenderer - - if renderer is None: - raise RuntimeError('No renderer defined') - if not self.get_visible(): - return - renderer.open_group('axes') - - locator = self.get_axes_locator() - if locator: - pos = locator(self, renderer) - self.apply_aspect(pos) - else: - self.apply_aspect() - - artists = [] - - artists.extend(self.collections) - artists.extend(self.patches) - artists.extend(self.lines) - artists.extend(self.texts) - artists.extend(self.artists) - if self.axison and not inframe: - if self._axisbelow: - self.xaxis.set_zorder(0.5) - self.yaxis.set_zorder(0.5) - else: - self.xaxis.set_zorder(2.5) - self.yaxis.set_zorder(2.5) - artists.extend([self.xaxis, self.yaxis]) - if not inframe: - artists.append(self.title) - artists.append(self._left_title) - artists.append(self._right_title) - artists.extend(self.tables) - if self.legend_ is not None: - artists.append(self.legend_) - - # the frame draws the edges around the axes patch -- we - # decouple these so the patch can be in the background and the - # frame in the foreground. - if self.axison and self._frameon: - artists.extend(self.spines.itervalues()) - - if self.figure.canvas.is_saving(): - dsu = [(a.zorder, a) for a in artists] - else: - dsu = [(a.zorder, a) for a in artists - if not a.get_animated()] - - # add images to dsu if the backend support compositing. - # otherwise, does the manaul compositing without adding images to dsu. - if len(self.images) <= 1 or renderer.option_image_nocomposite(): - dsu.extend([(im.zorder, im) for im in self.images]) - _do_composite = False - else: - _do_composite = True - - dsu.sort(key=itemgetter(0)) - - # rasterize artists with negative zorder - # if the minimum zorder is negative, start rasterization - rasterization_zorder = self._rasterization_zorder - if (rasterization_zorder is not None and - len(dsu) > 0 and dsu[0][0] < rasterization_zorder): - renderer.start_rasterizing() - dsu_rasterized = [l for l in dsu if l[0] < rasterization_zorder] - dsu = [l for l in dsu if l[0] >= rasterization_zorder] - else: - dsu_rasterized = [] - - # the patch draws the background rectangle -- the frame below - # will draw the edges - if self.axison and self._frameon: - self.patch.draw(renderer) - - if _do_composite: - # make a composite image blending alpha - # list of (mimage.Image, ox, oy) - - zorder_images = [(im.zorder, im) for im in self.images - if im.get_visible()] - zorder_images.sort(key=lambda x: x[0]) - - mag = renderer.get_image_magnification() - ims = [(im.make_image(mag), 0, 0, im.get_alpha()) for z, im in zorder_images] - - l, b, r, t = self.bbox.extents - width = mag * ((round(r) + 0.5) - (round(l) - 0.5)) - height = mag * ((round(t) + 0.5) - (round(b) - 0.5)) - im = mimage.from_images(height, - width, - ims) - - im.is_grayscale = False - l, b, w, h = self.bbox.bounds - # composite images need special args so they will not - # respect z-order for now - - gc = renderer.new_gc() - gc.set_clip_rectangle(self.bbox) - gc.set_clip_path(mtransforms.TransformedPath( - self.patch.get_path(), - self.patch.get_transform())) - - renderer.draw_image(gc, round(l), round(b), im) - gc.restore() - - if dsu_rasterized: - for zorder, a in dsu_rasterized: - a.draw(renderer) - renderer.stop_rasterizing() - - for zorder, a in dsu: - a.draw(renderer) - - renderer.close_group('axes') - self._cachedRenderer = renderer - - def draw_artist(self, a): - """ - This method can only be used after an initial draw which - caches the renderer. It is used to efficiently update Axes - data (axis ticks, labels, etc are not updated) - """ - assert self._cachedRenderer is not None - a.draw(self._cachedRenderer) - - def redraw_in_frame(self): - """ - This method can only be used after an initial draw which - caches the renderer. It is used to efficiently update Axes - data (axis ticks, labels, etc are not updated) - """ - assert self._cachedRenderer is not None - self.draw(self._cachedRenderer, inframe=True) - - def get_renderer_cache(self): - return self._cachedRenderer - - #### Axes rectangle characteristics - - def get_frame_on(self): - """ - Get whether the axes rectangle patch is drawn - """ - return self._frameon - - def set_frame_on(self, b): - """ - Set whether the axes rectangle patch is drawn - - ACCEPTS: [ *True* | *False* ] - """ - self._frameon = b - - def get_axisbelow(self): - """ - Get whether axis below is true or not - """ - return self._axisbelow - - def set_axisbelow(self, b): - """ - Set whether the axis ticks and gridlines are above or below most - artists - - ACCEPTS: [ *True* | *False* ] - """ - self._axisbelow = b - - @docstring.dedent_interpd - def grid(self, b=None, which='major', axis='both', **kwargs): - """ - Turn the axes grids on or off. - - Call signature:: - - grid(self, b=None, which='major', axis='both', **kwargs) - - Set the axes grids on or off; *b* is a boolean. (For MATLAB - compatibility, *b* may also be a string, 'on' or 'off'.) - - If *b* is *None* and ``len(kwargs)==0``, toggle the grid state. If - *kwargs* are supplied, it is assumed that you want a grid and *b* - is thus set to *True*. - - *which* can be 'major' (default), 'minor', or 'both' to control - whether major tick grids, minor tick grids, or both are affected. - - *axis* can be 'both' (default), 'x', or 'y' to control which - set of gridlines are drawn. - - *kwargs* are used to set the grid line properties, eg:: - - ax.grid(color='r', linestyle='-', linewidth=2) - - Valid :class:`~matplotlib.lines.Line2D` kwargs are - - %(Line2D)s - - """ - if len(kwargs): - b = True - b = _string_to_bool(b) - - if axis == 'x' or axis == 'both': - self.xaxis.grid(b, which=which, **kwargs) - if axis == 'y' or axis == 'both': - self.yaxis.grid(b, which=which, **kwargs) - - def ticklabel_format(self, **kwargs): - """ - Change the `~matplotlib.ticker.ScalarFormatter` used by - default for linear axes. - - Optional keyword arguments: - - ============ ========================================= - Keyword Description - ============ ========================================= - *style* [ 'sci' (or 'scientific') | 'plain' ] - plain turns off scientific notation - *scilimits* (m, n), pair of integers; if *style* - is 'sci', scientific notation will - be used for numbers outside the range - 10`m`:sup: to 10`n`:sup:. - Use (0,0) to include all numbers. - *useOffset* [True | False | offset]; if True, - the offset will be calculated as needed; - if False, no offset will be used; if a - numeric offset is specified, it will be - used. - *axis* [ 'x' | 'y' | 'both' ] - *useLocale* If True, format the number according to - the current locale. This affects things - such as the character used for the - decimal separator. If False, use - C-style (English) formatting. The - default setting is controlled by the - axes.formatter.use_locale rcparam. - ============ ========================================= - - Only the major ticks are affected. - If the method is called when the - :class:`~matplotlib.ticker.ScalarFormatter` is not the - :class:`~matplotlib.ticker.Formatter` being used, an - :exc:`AttributeError` will be raised. - - """ - style = kwargs.pop('style', '').lower() - scilimits = kwargs.pop('scilimits', None) - useOffset = kwargs.pop('useOffset', None) - useLocale = kwargs.pop('useLocale', None) - axis = kwargs.pop('axis', 'both').lower() - if scilimits is not None: - try: - m, n = scilimits - m + n + 1 # check that both are numbers - except (ValueError, TypeError): - raise ValueError("scilimits must be a sequence of 2 integers") - if style[:3] == 'sci': - sb = True - elif style in ['plain', 'comma']: - sb = False - if style == 'plain': - cb = False - else: - cb = True - raise NotImplementedError("comma style remains to be added") - elif style == '': - sb = None - else: - raise ValueError("%s is not a valid style value") - try: - if sb is not None: - if axis == 'both' or axis == 'x': - self.xaxis.major.formatter.set_scientific(sb) - if axis == 'both' or axis == 'y': - self.yaxis.major.formatter.set_scientific(sb) - if scilimits is not None: - if axis == 'both' or axis == 'x': - self.xaxis.major.formatter.set_powerlimits(scilimits) - if axis == 'both' or axis == 'y': - self.yaxis.major.formatter.set_powerlimits(scilimits) - if useOffset is not None: - if axis == 'both' or axis == 'x': - self.xaxis.major.formatter.set_useOffset(useOffset) - if axis == 'both' or axis == 'y': - self.yaxis.major.formatter.set_useOffset(useOffset) - if useLocale is not None: - if axis == 'both' or axis == 'x': - self.xaxis.major.formatter.set_useLocale(useLocale) - if axis == 'both' or axis == 'y': - self.yaxis.major.formatter.set_useLocale(useLocale) - except AttributeError: - raise AttributeError( - "This method only works with the ScalarFormatter.") - - def locator_params(self, axis='both', tight=None, **kwargs): - """ - Control behavior of tick locators. - - Keyword arguments: - - *axis* - ['x' | 'y' | 'both'] Axis on which to operate; - default is 'both'. - - *tight* - [True | False | None] Parameter passed to :meth:`autoscale_view`. - Default is None, for no change. - - Remaining keyword arguments are passed to directly to the - :meth:`~matplotlib.ticker.MaxNLocator.set_params` method. - - Typically one might want to reduce the maximum number - of ticks and use tight bounds when plotting small - subplots, for example:: - - ax.locator_params(tight=True, nbins=4) - - Because the locator is involved in autoscaling, - :meth:`autoscale_view` is called automatically after - the parameters are changed. - - This presently works only for the - :class:`~matplotlib.ticker.MaxNLocator` used - by default on linear axes, but it may be generalized. - """ - _x = axis in ['x', 'both'] - _y = axis in ['y', 'both'] - if _x: - self.xaxis.get_major_locator().set_params(**kwargs) - if _y: - self.yaxis.get_major_locator().set_params(**kwargs) - self.autoscale_view(tight=tight, scalex=_x, scaley=_y) - - def tick_params(self, axis='both', **kwargs): - """ - Change the appearance of ticks and tick labels. - - Keyword arguments: - - *axis* : ['x' | 'y' | 'both'] - Axis on which to operate; default is 'both'. - - *reset* : [True | False] - If *True*, set all parameters to defaults - before processing other keyword arguments. Default is - *False*. - - *which* : ['major' | 'minor' | 'both'] - Default is 'major'; apply arguments to *which* ticks. - - *direction* : ['in' | 'out' | 'inout'] - Puts ticks inside the axes, outside the axes, or both. - - *length* - Tick length in points. - - *width* - Tick width in points. - - *color* - Tick color; accepts any mpl color spec. - - *pad* - Distance in points between tick and label. - - *labelsize* - Tick label font size in points or as a string (e.g., 'large'). - - *labelcolor* - Tick label color; mpl color spec. - - *colors* - Changes the tick color and the label color to the same value: - mpl color spec. - - *zorder* - Tick and label zorder. - - *bottom*, *top*, *left*, *right* : [bool | 'on' | 'off'] - controls whether to draw the respective ticks. - - *labelbottom*, *labeltop*, *labelleft*, *labelright* - Boolean or ['on' | 'off'], controls whether to draw the - respective tick labels. - - Example:: - - ax.tick_params(direction='out', length=6, width=2, colors='r') - - This will make all major ticks be red, pointing out of the box, - and with dimensions 6 points by 2 points. Tick labels will - also be red. - - """ - if axis in ['x', 'both']: - xkw = dict(kwargs) - xkw.pop('left', None) - xkw.pop('right', None) - xkw.pop('labelleft', None) - xkw.pop('labelright', None) - self.xaxis.set_tick_params(**xkw) - if axis in ['y', 'both']: - ykw = dict(kwargs) - ykw.pop('top', None) - ykw.pop('bottom', None) - ykw.pop('labeltop', None) - ykw.pop('labelbottom', None) - self.yaxis.set_tick_params(**ykw) - - def set_axis_off(self): - """turn off the axis""" - self.axison = False - - def set_axis_on(self): - """turn on the axis""" - self.axison = True - - def get_axis_bgcolor(self): - """Return the axis background color""" - return self._axisbg - - def set_axis_bgcolor(self, color): - """ - set the axes background color - - ACCEPTS: any matplotlib color - see - :func:`~matplotlib.pyplot.colors` - """ - - self._axisbg = color - self.patch.set_facecolor(color) - - ### data limits, ticks, tick labels, and formatting - - def invert_xaxis(self): - "Invert the x-axis." - left, right = self.get_xlim() - self.set_xlim(right, left, auto=None) - - def xaxis_inverted(self): - """Returns *True* if the x-axis is inverted.""" - left, right = self.get_xlim() - return right < left - - def get_xbound(self): - """ - Returns the x-axis numerical bounds where:: - - lowerBound < upperBound - - """ - left, right = self.get_xlim() - if left < right: - return left, right - else: - return right, left - - def set_xbound(self, lower=None, upper=None): - """ - Set the lower and upper numerical bounds of the x-axis. - This method will honor axes inversion regardless of parameter order. - It will not change the _autoscaleXon attribute. - """ - if upper is None and iterable(lower): - lower, upper = lower - - old_lower, old_upper = self.get_xbound() - - if lower is None: - lower = old_lower - if upper is None: - upper = old_upper - - if self.xaxis_inverted(): - if lower < upper: - self.set_xlim(upper, lower, auto=None) - else: - self.set_xlim(lower, upper, auto=None) - else: - if lower < upper: - self.set_xlim(lower, upper, auto=None) - else: - self.set_xlim(upper, lower, auto=None) - - def get_xlim(self): - """ - Get the x-axis range [*left*, *right*] - """ - return tuple(self.viewLim.intervalx) - - def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): - """ - Call signature:: - - set_xlim(self, *args, **kwargs): - - Set the data limits for the xaxis - - Examples:: - - set_xlim((left, right)) - set_xlim(left, right) - set_xlim(left=1) # right unchanged - set_xlim(right=1) # left unchanged - - Keyword arguments: - - *left*: scalar - The left xlim; *xmin*, the previous name, may still be used - - *right*: scalar - The right xlim; *xmax*, the previous name, may still be used - - *emit*: [ *True* | *False* ] - Notify observers of limit change - - *auto*: [ *True* | *False* | *None* ] - Turn *x* autoscaling on (*True*), off (*False*; default), - or leave unchanged (*None*) - - Note, the *left* (formerly *xmin*) value may be greater than - the *right* (formerly *xmax*). - For example, suppose *x* is years before present. - Then one might use:: - - set_ylim(5000, 0) - - so 5000 years ago is on the left of the plot and the - present is on the right. - - Returns the current xlimits as a length 2 tuple - - ACCEPTS: length 2 sequence of floats - """ - if 'xmin' in kw: - left = kw.pop('xmin') - if 'xmax' in kw: - right = kw.pop('xmax') - if kw: - raise ValueError("unrecognized kwargs: %s" % kw.keys()) - - if right is None and iterable(left): - left, right = left - - self._process_unit_info(xdata=(left, right)) - if left is not None: - left = self.convert_xunits(left) - if right is not None: - right = self.convert_xunits(right) - - old_left, old_right = self.get_xlim() - if left is None: - left = old_left - if right is None: - right = old_right - - if left == right: - warnings.warn(('Attempting to set identical left==right results\n' - + 'in singular transformations; automatically expanding.\n' - + 'left=%s, right=%s') % (left, right)) - left, right = mtransforms.nonsingular(left, right, increasing=False) - left, right = self.xaxis.limit_range_for_scale(left, right) - - self.viewLim.intervalx = (left, right) - if auto is not None: - self._autoscaleXon = bool(auto) - - if emit: - self.callbacks.process('xlim_changed', self) - # Call all of the other x-axes that are shared with this one - for other in self._shared_x_axes.get_siblings(self): - if other is not self: - other.set_xlim(self.viewLim.intervalx, - emit=False, auto=auto) - if (other.figure != self.figure and - other.figure.canvas is not None): - other.figure.canvas.draw_idle() - - return left, right - - def get_xscale(self): - return self.xaxis.get_scale() - get_xscale.__doc__ = "Return the xaxis scale string: %s""" % ( - ", ".join(mscale.get_scale_names())) - - @docstring.dedent_interpd - def set_xscale(self, value, **kwargs): - """ - Call signature:: - - set_xscale(value) - - Set the scaling of the x-axis: %(scale)s - - ACCEPTS: [%(scale)s] - - Different kwargs are accepted, depending on the scale: - %(scale_docs)s - """ - self.xaxis._set_scale(value, **kwargs) - self.autoscale_view(scaley=False) - self._update_transScale() - - def get_xticks(self, minor=False): - """Return the x ticks as a list of locations""" - return self.xaxis.get_ticklocs(minor=minor) - - def set_xticks(self, ticks, minor=False): - """ - Set the x ticks with list of *ticks* - - ACCEPTS: sequence of floats - """ - return self.xaxis.set_ticks(ticks, minor=minor) - - def get_xmajorticklabels(self): - """ - Get the xtick labels as a list of :class:`~matplotlib.text.Text` - instances. - """ - return cbook.silent_list('Text xticklabel', - self.xaxis.get_majorticklabels()) - - def get_xminorticklabels(self): - """ - Get the x minor tick labels as a list of - :class:`matplotlib.text.Text` instances. - """ - return cbook.silent_list('Text xticklabel', - self.xaxis.get_minorticklabels()) - - def get_xticklabels(self, minor=False): - """ - Get the x tick labels as a list of :class:`~matplotlib.text.Text` - instances. - """ - return cbook.silent_list('Text xticklabel', - self.xaxis.get_ticklabels(minor=minor)) - - @docstring.dedent_interpd - def set_xticklabels(self, labels, fontdict=None, minor=False, **kwargs): - """ - Call signature:: - - set_xticklabels(labels, fontdict=None, minor=False, **kwargs) - - Set the xtick labels with list of strings *labels*. Return a - list of axis text instances. - - *kwargs* set the :class:`~matplotlib.text.Text` properties. - Valid properties are - %(Text)s - - ACCEPTS: sequence of strings - """ - return self.xaxis.set_ticklabels(labels, fontdict, - minor=minor, **kwargs) - - def invert_yaxis(self): - """ - Invert the y-axis. - """ - bottom, top = self.get_ylim() - self.set_ylim(top, bottom, auto=None) - - def yaxis_inverted(self): - """Returns *True* if the y-axis is inverted.""" - bottom, top = self.get_ylim() - return top < bottom - - def get_ybound(self): - """ - Return y-axis numerical bounds in the form of - ``lowerBound < upperBound`` - """ - bottom, top = self.get_ylim() - if bottom < top: - return bottom, top - else: - return top, bottom - - def set_ybound(self, lower=None, upper=None): - """ - Set the lower and upper numerical bounds of the y-axis. - This method will honor axes inversion regardless of parameter order. - It will not change the _autoscaleYon attribute. - """ - if upper is None and iterable(lower): - lower, upper = lower - - old_lower, old_upper = self.get_ybound() - - if lower is None: - lower = old_lower - if upper is None: - upper = old_upper - - if self.yaxis_inverted(): - if lower < upper: - self.set_ylim(upper, lower, auto=None) - else: - self.set_ylim(lower, upper, auto=None) - else: - if lower < upper: - self.set_ylim(lower, upper, auto=None) - else: - self.set_ylim(upper, lower, auto=None) - - def get_ylim(self): - """ - Get the y-axis range [*bottom*, *top*] - """ - return tuple(self.viewLim.intervaly) - - def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): - """ - Call signature:: - - set_ylim(self, *args, **kwargs): - - Set the data limits for the yaxis - - Examples:: - - set_ylim((bottom, top)) - set_ylim(bottom, top) - set_ylim(bottom=1) # top unchanged - set_ylim(top=1) # bottom unchanged - - Keyword arguments: - - *bottom*: scalar - The bottom ylim; the previous name, *ymin*, may still be used - - *top*: scalar - The top ylim; the previous name, *ymax*, may still be used - - *emit*: [ *True* | *False* ] - Notify observers of limit change - - *auto*: [ *True* | *False* | *None* ] - Turn *y* autoscaling on (*True*), off (*False*; default), - or leave unchanged (*None*) - - Note, the *bottom* (formerly *ymin*) value may be greater than - the *top* (formerly *ymax*). - For example, suppose *y* is depth in the ocean. - Then one might use:: - - set_ylim(5000, 0) - - so 5000 m depth is at the bottom of the plot and the - surface, 0 m, is at the top. - - Returns the current ylimits as a length 2 tuple - - ACCEPTS: length 2 sequence of floats - """ - if 'ymin' in kw: - bottom = kw.pop('ymin') - if 'ymax' in kw: - top = kw.pop('ymax') - if kw: - raise ValueError("unrecognized kwargs: %s" % kw.keys()) - - if top is None and iterable(bottom): - bottom, top = bottom - - if bottom is not None: - bottom = self.convert_yunits(bottom) - if top is not None: - top = self.convert_yunits(top) - - old_bottom, old_top = self.get_ylim() - - if bottom is None: - bottom = old_bottom - if top is None: - top = old_top - - if bottom == top: - warnings.warn(('Attempting to set identical bottom==top results\n' - + 'in singular transformations; automatically expanding.\n' - + 'bottom=%s, top=%s') % (bottom, top)) - - bottom, top = mtransforms.nonsingular(bottom, top, increasing=False) - bottom, top = self.yaxis.limit_range_for_scale(bottom, top) - - self.viewLim.intervaly = (bottom, top) - if auto is not None: - self._autoscaleYon = bool(auto) - - if emit: - self.callbacks.process('ylim_changed', self) - # Call all of the other y-axes that are shared with this one - for other in self._shared_y_axes.get_siblings(self): - if other is not self: - other.set_ylim(self.viewLim.intervaly, - emit=False, auto=auto) - if (other.figure != self.figure and - other.figure.canvas is not None): - other.figure.canvas.draw_idle() - - return bottom, top - - def get_yscale(self): - return self.yaxis.get_scale() - get_yscale.__doc__ = "Return the yaxis scale string: %s""" % ( - ", ".join(mscale.get_scale_names())) - - @docstring.dedent_interpd - def set_yscale(self, value, **kwargs): - """ - Call signature:: - - set_yscale(value) - - Set the scaling of the y-axis: %(scale)s - - ACCEPTS: [%(scale)s] - - Different kwargs are accepted, depending on the scale: - %(scale_docs)s - """ - self.yaxis._set_scale(value, **kwargs) - self.autoscale_view(scalex=False) - self._update_transScale() - - def get_yticks(self, minor=False): - """Return the y ticks as a list of locations""" - return self.yaxis.get_ticklocs(minor=minor) - - def set_yticks(self, ticks, minor=False): - """ - Set the y ticks with list of *ticks* - - ACCEPTS: sequence of floats - - Keyword arguments: - - *minor*: [ *False* | *True* ] - Sets the minor ticks if *True* - """ - return self.yaxis.set_ticks(ticks, minor=minor) - - def get_ymajorticklabels(self): - """ - Get the major y tick labels as a list of - :class:`~matplotlib.text.Text` instances. - """ - return cbook.silent_list('Text yticklabel', - self.yaxis.get_majorticklabels()) - - def get_yminorticklabels(self): - """ - Get the minor y tick labels as a list of - :class:`~matplotlib.text.Text` instances. - """ - return cbook.silent_list('Text yticklabel', - self.yaxis.get_minorticklabels()) - - def get_yticklabels(self, minor=False): - """ - Get the y tick labels as a list of :class:`~matplotlib.text.Text` - instances - """ - return cbook.silent_list('Text yticklabel', - self.yaxis.get_ticklabels(minor=minor)) - - @docstring.dedent_interpd - def set_yticklabels(self, labels, fontdict=None, minor=False, **kwargs): - """ - Call signature:: - - set_yticklabels(labels, fontdict=None, minor=False, **kwargs) - - Set the y tick labels with list of strings *labels*. Return a list of - :class:`~matplotlib.text.Text` instances. - - *kwargs* set :class:`~matplotlib.text.Text` properties for the labels. - Valid properties are - %(Text)s - - ACCEPTS: sequence of strings - """ - return self.yaxis.set_ticklabels(labels, fontdict, - minor=minor, **kwargs) - - def xaxis_date(self, tz=None): - """ - Sets up x-axis ticks and labels that treat the x data as dates. - - *tz* is a timezone string or :class:`tzinfo` instance. - Defaults to rc value. - """ - # should be enough to inform the unit conversion interface - # dates are coming in - self.xaxis.axis_date(tz) - - def yaxis_date(self, tz=None): - """ - Sets up y-axis ticks and labels that treat the y data as dates. - - *tz* is a timezone string or :class:`tzinfo` instance. - Defaults to rc value. - """ - self.yaxis.axis_date(tz) - - def format_xdata(self, x): - """ - Return *x* string formatted. This function will use the attribute - self.fmt_xdata if it is callable, else will fall back on the xaxis - major formatter - """ - try: - return self.fmt_xdata(x) - except TypeError: - func = self.xaxis.get_major_formatter().format_data_short - val = func(x) - return val - - def format_ydata(self, y): - """ - Return y string formatted. This function will use the - :attr:`fmt_ydata` attribute if it is callable, else will fall - back on the yaxis major formatter - """ - try: - return self.fmt_ydata(y) - except TypeError: - func = self.yaxis.get_major_formatter().format_data_short - val = func(y) - return val - - def format_coord(self, x, y): - """Return a format string formatting the *x*, *y* coord""" - if x is None: - xs = '???' - else: - xs = self.format_xdata(x) - if y is None: - ys = '???' - else: - ys = self.format_ydata(y) - return 'x=%s y=%s' % (xs, ys) - - #### Interactive manipulation - - def can_zoom(self): - """ - Return *True* if this axes supports the zoom box button functionality. - """ - return True - - def can_pan(self): - """ - Return *True* if this axes supports any pan/zoom button functionality. - """ - return True - - def get_navigate(self): - """ - Get whether the axes responds to navigation commands - """ - return self._navigate - - def set_navigate(self, b): - """ - Set whether the axes responds to navigation toolbar commands - - ACCEPTS: [ *True* | *False* ] - """ - self._navigate = b - - def get_navigate_mode(self): - """ - Get the navigation toolbar button status: 'PAN', 'ZOOM', or None - """ - return self._navigate_mode - - def set_navigate_mode(self, b): - """ - Set the navigation toolbar button status; - - .. warning:: - this is not a user-API function. - - """ - self._navigate_mode = b - - def start_pan(self, x, y, button): - """ - Called when a pan operation has started. - - *x*, *y* are the mouse coordinates in display coords. - button is the mouse button number: - - * 1: LEFT - * 2: MIDDLE - * 3: RIGHT - - .. note:: - - Intended to be overridden by new projection types. - - """ - self._pan_start = cbook.Bunch( - lim=self.viewLim.frozen(), - trans=self.transData.frozen(), - trans_inverse=self.transData.inverted().frozen(), - bbox=self.bbox.frozen(), - x=x, - y=y - ) - - def end_pan(self): - """ - Called when a pan operation completes (when the mouse button - is up.) - - .. note:: - - Intended to be overridden by new projection types. - - """ - del self._pan_start - - def drag_pan(self, button, key, x, y): - """ - Called when the mouse moves during a pan operation. - - *button* is the mouse button number: - - * 1: LEFT - * 2: MIDDLE - * 3: RIGHT - - *key* is a "shift" key - - *x*, *y* are the mouse coordinates in display coords. - - .. note:: - - Intended to be overridden by new projection types. - - """ - def format_deltas(key, dx, dy): - if key == 'control': - if abs(dx) > abs(dy): - dy = dx - else: - dx = dy - elif key == 'x': - dy = 0 - elif key == 'y': - dx = 0 - elif key == 'shift': - if 2 * abs(dx) < abs(dy): - dx = 0 - elif 2 * abs(dy) < abs(dx): - dy = 0 - elif abs(dx) > abs(dy): - dy = dy / abs(dy) * abs(dx) - else: - dx = dx / abs(dx) * abs(dy) - return (dx, dy) - - p = self._pan_start - dx = x - p.x - dy = y - p.y - if dx == 0 and dy == 0: - return - if button == 1: - dx, dy = format_deltas(key, dx, dy) - result = p.bbox.translated(-dx, -dy) \ - .transformed(p.trans_inverse) - elif button == 3: - try: - dx = -dx / float(self.bbox.width) - dy = -dy / float(self.bbox.height) - dx, dy = format_deltas(key, dx, dy) - if self.get_aspect() != 'auto': - dx = 0.5 * (dx + dy) - dy = dx - - alpha = np.power(10.0, (dx, dy)) - start = np.array([p.x, p.y]) - oldpoints = p.lim.transformed(p.trans) - newpoints = start + alpha * (oldpoints - start) - result = mtransforms.Bbox(newpoints) \ - .transformed(p.trans_inverse) - except OverflowError: - warnings.warn('Overflow while panning') - return - - self.set_xlim(*result.intervalx) - self.set_ylim(*result.intervaly) - - def get_cursor_props(self): - """ - Return the cursor propertiess as a (*linewidth*, *color*) - tuple, where *linewidth* is a float and *color* is an RGBA - tuple - """ - return self._cursorProps - - def set_cursor_props(self, *args): - """ - Set the cursor property as:: - - ax.set_cursor_props(linewidth, color) - - or:: - - ax.set_cursor_props((linewidth, color)) - - ACCEPTS: a (*float*, *color*) tuple - """ - if len(args) == 1: - lw, c = args[0] - elif len(args) == 2: - lw, c = args - else: - raise ValueError('args must be a (linewidth, color) tuple') - c = mcolors.colorConverter.to_rgba(c) - self._cursorProps = lw, c - - def get_children(self): - """return a list of child artists""" - children = [] - children.append(self.xaxis) - children.append(self.yaxis) - children.extend(self.lines) - children.extend(self.patches) - children.extend(self.texts) - children.extend(self.tables) - children.extend(self.artists) - children.extend(self.images) - if self.legend_ is not None: - children.append(self.legend_) - children.extend(self.collections) - children.append(self.title) - children.append(self._left_title) - children.append(self._right_title) - children.append(self.patch) - children.extend(self.spines.itervalues()) - return children - - def contains(self, mouseevent): - """ - Test whether the mouse event occured in the axes. - - Returns *True* / *False*, {} - """ - if callable(self._contains): - return self._contains(self, mouseevent) - - return self.patch.contains(mouseevent) - - def contains_point(self, point): - """ - Returns *True* if the point (tuple of x,y) is inside the axes - (the area defined by the its patch). A pixel coordinate is - required. - - """ - return self.patch.contains_point(point, radius=1.0) - - def pick(self, *args): - """ - Call signature:: - - pick(mouseevent) - - each child artist will fire a pick event if mouseevent is over - the artist and the artist has picker set - """ - martist.Artist.pick(self, args[0]) +class Axes(_AxesBase): + """ + The :class:`Axes` contains most of the figure elements: + :class:`~matplotlib.axis.Axis`, :class:`~matplotlib.axis.Tick`, + :class:`~matplotlib.lines.Line2D`, :class:`~matplotlib.text.Text`, + :class:`~matplotlib.patches.Polygon`, etc., and sets the + coordinate system. + The :class:`Axes` instance supports callbacks through a callbacks + attribute which is a :class:`~matplotlib.cbook.CallbackRegistry` + instance. The events you can connect to are 'xlim_changed' and + 'ylim_changed' and the callback will be called with func(*ax*) + where *ax* is the :class:`Axes` instance. + """ ### Labelling def get_title(self, loc="center"): @@ -3404,7 +305,7 @@ def annotate(self, *args, **kwargs): a = mtext.Annotation(*args, **kwargs) a.set_transform(mtransforms.IdentityTransform()) self._set_artist_props(a) - if kwargs.has_key('clip_on'): + if 'clip_on' in kwargs: a.set_clip_path(self.patch) self.texts.append(a) a._remove_method = lambda h: self.texts.remove(h) @@ -5735,7 +2636,7 @@ def xywhere(xs, ys, mask): rightup, yup = xywhere(right, y, xuplims & everymask) caplines.extend( - self.plot(rightup, yup, ls='None', + self.plot(rightup, yup, ls='None', marker=mlines.CARETRIGHT, **plot_kw)) xuplims = ~xuplims rightup, yup = xywhere(right, y, xuplims & everymask) @@ -6624,7 +3525,7 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None, polygon = np.zeros((6, 2), float) polygon[:, 0] = sx * np.array([0.5, 0.5, 0.0, -0.5, -0.5, 0.0]) - polygon[:, 1] = sy * np.array([-0.5, 0.5, 1.0, 0.5, -0.5, -1.0]) / 3.0 + polygon[:, 1] = sy * np.array([-0.5, 0.5, 1.0, 0.5, -0.5, -1.0]) / 3.0 if edgecolors == 'none': edgecolors = 'face' @@ -7370,11 +4271,11 @@ def _pcolorargs(funcname, *args, **kw): ' X (%d) and/or Y (%d); see help(%s)' % ( C.shape, Nx, Ny, funcname)) else: - if not (numCols in (Nx, Nx-1) and numRows in (Ny, Ny-1)): + if not (numCols in (Nx, Nx - 1) and numRows in (Ny, Ny - 1)): raise TypeError('Dimensions of C %s are incompatible with' ' X (%d) and/or Y (%d); see help(%s)' % ( C.shape, Nx, Ny, funcname)) - C = C[:Ny-1, :Nx-1] + C = C[:Ny - 1, :Nx - 1] return X, Y, C @docstring.dedent_interpd @@ -9196,211 +6097,3 @@ def tripcolor(self, *args, **kwargs): def triplot(self, *args, **kwargs): mtri.triplot(self, *args, **kwargs) triplot.__doc__ = mtri.triplot.__doc__ - - -from matplotlib.gridspec import GridSpec, SubplotSpec - - -class SubplotBase: - """ - Base class for subplots, which are :class:`Axes` instances with - additional methods to facilitate generating and manipulating a set - of :class:`Axes` within a figure. - """ - - def __init__(self, fig, *args, **kwargs): - """ - *fig* is a :class:`matplotlib.figure.Figure` instance. - - *args* is the tuple (*numRows*, *numCols*, *plotNum*), where - the array of subplots in the figure has dimensions *numRows*, - *numCols*, and where *plotNum* is the number of the subplot - being created. *plotNum* starts at 1 in the upper left - corner and increases to the right. - - - If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the - decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*. - """ - - self.figure = fig - - if len(args) == 1: - if isinstance(args[0], SubplotSpec): - self._subplotspec = args[0] - else: - try: - s = str(int(args[0])) - rows, cols, num = map(int, s) - except ValueError: - raise ValueError( - 'Single argument to subplot must be a 3-digit ' - 'integer') - self._subplotspec = GridSpec(rows, cols)[num - 1] - # num - 1 for converting from MATLAB to python indexing - elif len(args) == 3: - rows, cols, num = args - rows = int(rows) - cols = int(cols) - if isinstance(num, tuple) and len(num) == 2: - num = [int(n) for n in num] - self._subplotspec = GridSpec(rows, cols)[num[0] - 1:num[1]] - else: - self._subplotspec = GridSpec(rows, cols)[int(num) - 1] - # num - 1 for converting from MATLAB to python indexing - else: - raise ValueError('Illegal argument(s) to subplot: %s' % (args,)) - - self.update_params() - - # _axes_class is set in the subplot_class_factory - self._axes_class.__init__(self, fig, self.figbox, **kwargs) - - def __reduce__(self): - # get the first axes class which does not inherit from a subplotbase - not_subplotbase = lambda c: issubclass(c, Axes) and \ - not issubclass(c, SubplotBase) - axes_class = [c for c in self.__class__.mro() if not_subplotbase(c)][0] - r = [_PicklableSubplotClassConstructor(), - (axes_class,), - self.__getstate__()] - return tuple(r) - - def get_geometry(self): - """get the subplot geometry, eg 2,2,3""" - rows, cols, num1, num2 = self.get_subplotspec().get_geometry() - return rows, cols, num1 + 1 # for compatibility - - # COVERAGE NOTE: Never used internally or from examples - def change_geometry(self, numrows, numcols, num): - """change subplot geometry, e.g., from 1,1,1 to 2,2,3""" - self._subplotspec = GridSpec(numrows, numcols)[num - 1] - self.update_params() - self.set_position(self.figbox) - - def get_subplotspec(self): - """get the SubplotSpec instance associated with the subplot""" - return self._subplotspec - - def set_subplotspec(self, subplotspec): - """set the SubplotSpec instance associated with the subplot""" - self._subplotspec = subplotspec - - def update_params(self): - """update the subplot position from fig.subplotpars""" - - self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = \ - self.get_subplotspec().get_position(self.figure, - return_all=True) - - def is_first_col(self): - return self.colNum == 0 - - def is_first_row(self): - return self.rowNum == 0 - - def is_last_row(self): - return self.rowNum == self.numRows - 1 - - def is_last_col(self): - return self.colNum == self.numCols - 1 - - # COVERAGE NOTE: Never used internally or from examples - def label_outer(self): - """ - set the visible property on ticklabels so xticklabels are - visible only if the subplot is in the last row and yticklabels - are visible only if the subplot is in the first column - """ - lastrow = self.is_last_row() - firstcol = self.is_first_col() - for label in self.get_xticklabels(): - label.set_visible(lastrow) - - for label in self.get_yticklabels(): - label.set_visible(firstcol) - - def _make_twin_axes(self, *kl, **kwargs): - """ - make a twinx axes of self. This is used for twinx and twiny. - """ - from matplotlib.projections import process_projection_requirements - kl = (self.get_subplotspec(),) + kl - projection_class, kwargs, key = process_projection_requirements( - self.figure, *kl, **kwargs) - - ax2 = subplot_class_factory(projection_class)(self.figure, - *kl, **kwargs) - self.figure.add_subplot(ax2) - return ax2 - -_subplot_classes = {} - - -def subplot_class_factory(axes_class=None): - # This makes a new class that inherits from SubplotBase and the - # given axes_class (which is assumed to be a subclass of Axes). - # This is perhaps a little bit roundabout to make a new class on - # the fly like this, but it means that a new Subplot class does - # not have to be created for every type of Axes. - if axes_class is None: - axes_class = Axes - - new_class = _subplot_classes.get(axes_class) - if new_class is None: - new_class = type("%sSubplot" % (axes_class.__name__), - (SubplotBase, axes_class), - {'_axes_class': axes_class}) - _subplot_classes[axes_class] = new_class - - return new_class - -# This is provided for backward compatibility -Subplot = subplot_class_factory() - - -class _PicklableSubplotClassConstructor(object): - """ - This stub class exists to return the appropriate subplot - class when __call__-ed with an axes class. This is purely to - allow Pickling of Axes and Subplots. - """ - def __call__(self, axes_class): - # create a dummy object instance - subplot_instance = _PicklableSubplotClassConstructor() - subplot_class = subplot_class_factory(axes_class) - # update the class to the desired subplot class - subplot_instance.__class__ = subplot_class - return subplot_instance - - -docstring.interpd.update(Axes=martist.kwdoc(Axes)) -docstring.interpd.update(Subplot=martist.kwdoc(Axes)) - -""" -# this is some discarded code I was using to find the minimum positive -# data point for some log scaling fixes. I realized there was a -# cleaner way to do it, but am keeping this around as an example for -# how to get the data out of the axes. Might want to make something -# like this a method one day, or better yet make get_verts an Artist -# method - - minx, maxx = self.get_xlim() - if minx<=0 or maxx<=0: - # find the min pos value in the data - xs = [] - for line in self.lines: - xs.extend(line.get_xdata(orig=False)) - for patch in self.patches: - xs.extend([x for x,y in patch.get_verts()]) - for collection in self.collections: - xs.extend([x for x,y in collection.get_verts()]) - posx = [x for x in xs if x>0] - if len(posx): - - minx = min(posx) - maxx = max(posx) - # warning, probably breaks inverted axis - self.set_xlim((0.1*minx, maxx)) - -""" diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py new file mode 100644 index 000000000000..b4cead3ad71d --- /dev/null +++ b/lib/matplotlib/axes/_base.py @@ -0,0 +1,3130 @@ +import itertools +import warnings +import math +from operator import itemgetter + +import numpy as np +from numpy import ma + +import matplotlib +rcParams = matplotlib.rcParams + +from matplotlib import cbook +from matplotlib import docstring +import matplotlib.colors as mcolors +import matplotlib.lines as mlines +import matplotlib.patches as mpatches +import matplotlib.artist as martist +import matplotlib.transforms as mtransforms +import matplotlib.axis as maxis +import matplotlib.scale as mscale +import matplotlib.spines as mspines +import matplotlib.font_manager as font_manager +import matplotlib.text as mtext +import matplotlib.image as mimage +from matplotlib.artist import allow_rasterization + + +from matplotlib.cbook import iterable + + +is_string_like = cbook.is_string_like +is_sequence_of_strings = cbook.is_sequence_of_strings + + +def _string_to_bool(s): + if not is_string_like(s): + return s + if s == 'on': + return True + if s == 'off': + return False + raise ValueError("string argument must be either 'on' or 'off'") + + +def _process_plot_format(fmt): + """ + Process a MATLAB style color/line style format string. Return a + (*linestyle*, *color*) tuple as a result of the processing. Default + values are ('-', 'b'). Example format strings include: + + * 'ko': black circles + * '.b': blue dots + * 'r--': red dashed lines + + .. seealso:: + + :func:`~matplotlib.Line2D.lineStyles` and + :func:`~matplotlib.pyplot.colors` + for all possible styles and color format string. + """ + + linestyle = None + marker = None + color = None + + # Is fmt just a colorspec? + try: + color = mcolors.colorConverter.to_rgb(fmt) + + # We need to differentiate grayscale '1.0' from tri_down marker '1' + try: + fmtint = str(int(fmt)) + except ValueError: + return linestyle, marker, color # Yes + else: + if fmt != fmtint: + # user definitely doesn't want tri_down marker + return linestyle, marker, color # Yes + else: + # ignore converted color + color = None + except ValueError: + pass # No, not just a color. + + # handle the multi char special cases and strip them from the + # string + if fmt.find('--') >= 0: + linestyle = '--' + fmt = fmt.replace('--', '') + if fmt.find('-.') >= 0: + linestyle = '-.' + fmt = fmt.replace('-.', '') + if fmt.find(' ') >= 0: + linestyle = 'None' + fmt = fmt.replace(' ', '') + + chars = [c for c in fmt] + + for c in chars: + if c in mlines.lineStyles: + if linestyle is not None: + raise ValueError( + 'Illegal format string "%s"; two linestyle symbols' % fmt) + linestyle = c + elif c in mlines.lineMarkers: + if marker is not None: + raise ValueError( + 'Illegal format string "%s"; two marker symbols' % fmt) + marker = c + elif c in mcolors.colorConverter.colors: + if color is not None: + raise ValueError( + 'Illegal format string "%s"; two color symbols' % fmt) + color = c + else: + raise ValueError( + 'Unrecognized character %c in format string' % c) + + if linestyle is None and marker is None: + linestyle = rcParams['lines.linestyle'] + if linestyle is None: + linestyle = 'None' + if marker is None: + marker = 'None' + + return linestyle, marker, color + + +class _process_plot_var_args(object): + """ + Process variable length arguments to the plot command, so that + plot commands like the following are supported:: + + plot(t, s) + plot(t1, s1, t2, s2) + plot(t1, s1, 'ko', t2, s2) + plot(t1, s1, 'ko', t2, s2, 'r--', t3, e3) + + an arbitrary number of *x*, *y*, *fmt* are allowed + """ + def __init__(self, axes, command='plot'): + self.axes = axes + self.command = command + self.set_color_cycle() + + def __getstate__(self): + # note: it is not possible to pickle a itertools.cycle instance + return {'axes': self.axes, 'command': self.command} + + def __setstate__(self, state): + self.__dict__ = state.copy() + self.set_color_cycle() + + def set_color_cycle(self, clist=None): + if clist is None: + clist = rcParams['axes.color_cycle'] + self.color_cycle = itertools.cycle(clist) + + def __call__(self, *args, **kwargs): + + if self.axes.xaxis is not None and self.axes.yaxis is not None: + xunits = kwargs.pop('xunits', self.axes.xaxis.units) + + if self.axes.name == 'polar': + xunits = kwargs.pop('thetaunits', xunits) + + yunits = kwargs.pop('yunits', self.axes.yaxis.units) + + if self.axes.name == 'polar': + yunits = kwargs.pop('runits', yunits) + + if xunits != self.axes.xaxis.units: + self.axes.xaxis.set_units(xunits) + + if yunits != self.axes.yaxis.units: + self.axes.yaxis.set_units(yunits) + + ret = self._grab_next_args(*args, **kwargs) + return ret + + def set_lineprops(self, line, **kwargs): + assert self.command == 'plot', 'set_lineprops only works with "plot"' + for key, val in kwargs.items(): + funcName = "set_%s" % key + if not hasattr(line, funcName): + raise TypeError('There is no line property "%s"' % key) + func = getattr(line, funcName) + func(val) + + def set_patchprops(self, fill_poly, **kwargs): + assert self.command == 'fill', 'set_patchprops only works with "fill"' + for key, val in kwargs.items(): + funcName = "set_%s" % key + if not hasattr(fill_poly, funcName): + raise TypeError('There is no patch property "%s"' % key) + func = getattr(fill_poly, funcName) + func(val) + + def _xy_from_xy(self, x, y): + if self.axes.xaxis is not None and self.axes.yaxis is not None: + bx = self.axes.xaxis.update_units(x) + by = self.axes.yaxis.update_units(y) + + if self.command != 'plot': + # the Line2D class can handle unitized data, with + # support for post hoc unit changes etc. Other mpl + # artists, eg Polygon which _process_plot_var_args + # also serves on calls to fill, cannot. So this is a + # hack to say: if you are not "plot", which is + # creating Line2D, then convert the data now to + # floats. If you are plot, pass the raw data through + # to Line2D which will handle the conversion. So + # polygons will not support post hoc conversions of + # the unit type since they are not storing the orig + # data. Hopefully we can rationalize this at a later + # date - JDH + if bx: + x = self.axes.convert_xunits(x) + if by: + y = self.axes.convert_yunits(y) + + x = np.atleast_1d(x) # like asanyarray, but converts scalar to array + y = np.atleast_1d(y) + if x.shape[0] != y.shape[0]: + raise ValueError("x and y must have same first dimension") + if x.ndim > 2 or y.ndim > 2: + raise ValueError("x and y can be no greater than 2-D") + + if x.ndim == 1: + x = x[:, np.newaxis] + if y.ndim == 1: + y = y[:, np.newaxis] + return x, y + + def _makeline(self, x, y, kw, kwargs): + kw = kw.copy() # Don't modify the original kw. + if not 'color' in kw and not 'color' in kwargs.keys(): + kw['color'] = self.color_cycle.next() + # (can't use setdefault because it always evaluates + # its second argument) + seg = mlines.Line2D(x, y, + axes=self.axes, + **kw + ) + self.set_lineprops(seg, **kwargs) + return seg + + def _makefill(self, x, y, kw, kwargs): + try: + facecolor = kw['color'] + except KeyError: + facecolor = self.color_cycle.next() + seg = mpatches.Polygon(np.hstack((x[:, np.newaxis], + y[:, np.newaxis])), + facecolor=facecolor, + fill=True, + closed=kw['closed']) + self.set_patchprops(seg, **kwargs) + return seg + + def _plot_args(self, tup, kwargs): + ret = [] + if len(tup) > 1 and is_string_like(tup[-1]): + linestyle, marker, color = _process_plot_format(tup[-1]) + tup = tup[:-1] + elif len(tup) == 3: + raise ValueError('third arg must be a format string') + else: + linestyle, marker, color = None, None, None + kw = {} + for k, v in zip(('linestyle', 'marker', 'color'), + (linestyle, marker, color)): + if v is not None: + kw[k] = v + + y = np.atleast_1d(tup[-1]) + + if len(tup) == 2: + x = np.atleast_1d(tup[0]) + else: + x = np.arange(y.shape[0], dtype=float) + + x, y = self._xy_from_xy(x, y) + + if self.command == 'plot': + func = self._makeline + else: + kw['closed'] = kwargs.get('closed', True) + func = self._makefill + + ncx, ncy = x.shape[1], y.shape[1] + for j in xrange(max(ncx, ncy)): + seg = func(x[:, j % ncx], y[:, j % ncy], kw, kwargs) + ret.append(seg) + return ret + + def _grab_next_args(self, *args, **kwargs): + + remaining = args + while 1: + + if len(remaining) == 0: + return + if len(remaining) <= 3: + for seg in self._plot_args(remaining, kwargs): + yield seg + return + + if is_string_like(remaining[2]): + isplit = 3 + else: + isplit = 2 + + for seg in self._plot_args(remaining[:isplit], kwargs): + yield seg + remaining = remaining[isplit:] + + +class _AxesBase(martist.Artist): + """ + """ + name = "rectilinear" + + _shared_x_axes = cbook.Grouper() + _shared_y_axes = cbook.Grouper() + + def __str__(self): + return "Axes(%g,%g;%gx%g)" % tuple(self._position.bounds) + + def __init__(self, fig, rect, + axisbg=None, # defaults to rc axes.facecolor + frameon=True, + sharex=None, # use Axes instance's xaxis info + sharey=None, # use Axes instance's yaxis info + label='', + xscale=None, + yscale=None, + **kwargs + ): + """ + Build an :class:`Axes` instance in + :class:`~matplotlib.figure.Figure` *fig* with + *rect=[left, bottom, width, height]* in + :class:`~matplotlib.figure.Figure` coordinates + + Optional keyword arguments: + + ================ ========================================= + Keyword Description + ================ ========================================= + *adjustable* [ 'box' | 'datalim' | 'box-forced'] + *alpha* float: the alpha transparency (can be None) + *anchor* [ 'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', + 'NW', 'W' ] + *aspect* [ 'auto' | 'equal' | aspect_ratio ] + *autoscale_on* [ *True* | *False* ] whether or not to + autoscale the *viewlim* + *axis_bgcolor* any matplotlib color, see + :func:`~matplotlib.pyplot.colors` + *axisbelow* draw the grids and ticks below the other + artists + *cursor_props* a (*float*, *color*) tuple + *figure* a :class:`~matplotlib.figure.Figure` + instance + *frame_on* a boolean - draw the axes frame + *label* the axes label + *navigate* [ *True* | *False* ] + *navigate_mode* [ 'PAN' | 'ZOOM' | None ] the navigation + toolbar button status + *position* [left, bottom, width, height] in + class:`~matplotlib.figure.Figure` coords + *sharex* an class:`~matplotlib.axes.Axes` instance + to share the x-axis with + *sharey* an class:`~matplotlib.axes.Axes` instance + to share the y-axis with + *title* the title string + *visible* [ *True* | *False* ] whether the axes is + visible + *xlabel* the xlabel + *xlim* (*xmin*, *xmax*) view limits + *xscale* [%(scale)s] + *xticklabels* sequence of strings + *xticks* sequence of floats + *ylabel* the ylabel strings + *ylim* (*ymin*, *ymax*) view limits + *yscale* [%(scale)s] + *yticklabels* sequence of strings + *yticks* sequence of floats + ================ ========================================= + """ % {'scale': ' | '.join( + [repr(x) for x in mscale.get_scale_names()])} + martist.Artist.__init__(self) + if isinstance(rect, mtransforms.Bbox): + self._position = rect + else: + self._position = mtransforms.Bbox.from_bounds(*rect) + self._originalPosition = self._position.frozen() + self.set_axes(self) + self.set_aspect('auto') + self._adjustable = 'box' + self.set_anchor('C') + self._sharex = sharex + self._sharey = sharey + if sharex is not None: + self._shared_x_axes.join(self, sharex) + if sharex._adjustable == 'box': + sharex._adjustable = 'datalim' + #warnings.warn( + # 'shared axes: "adjustable" is being changed to "datalim"') + self._adjustable = 'datalim' + if sharey is not None: + self._shared_y_axes.join(self, sharey) + if sharey._adjustable == 'box': + sharey._adjustable = 'datalim' + #warnings.warn( + # 'shared axes: "adjustable" is being changed to "datalim"') + self._adjustable = 'datalim' + self.set_label(label) + self.set_figure(fig) + + self.set_axes_locator(kwargs.get("axes_locator", None)) + + self.spines = self._gen_axes_spines() + + # this call may differ for non-sep axes, eg polar + self._init_axis() + + if axisbg is None: + axisbg = rcParams['axes.facecolor'] + self._axisbg = axisbg + self._frameon = frameon + self._axisbelow = rcParams['axes.axisbelow'] + + self._rasterization_zorder = None + + self._hold = rcParams['axes.hold'] + self._connected = {} # a dict from events to (id, func) + self.cla() + # funcs used to format x and y - fall back on major formatters + self.fmt_xdata = None + self.fmt_ydata = None + + self.set_cursor_props((1, 'k')) # set the cursor properties for axes + + self._cachedRenderer = None + self.set_navigate(True) + self.set_navigate_mode(None) + + if xscale: + self.set_xscale(xscale) + if yscale: + self.set_yscale(yscale) + + if len(kwargs): + martist.setp(self, **kwargs) + + if self.xaxis is not None: + self._xcid = self.xaxis.callbacks.connect('units finalize', + self.relim) + + if self.yaxis is not None: + self._ycid = self.yaxis.callbacks.connect('units finalize', + self.relim) + + def __setstate__(self, state): + self.__dict__ = state + # put the _remove_method back on all artists contained within the axes + for container_name in ['lines', 'collections', 'tables', 'patches', + 'texts', 'images']: + container = getattr(self, container_name) + for artist in container: + artist._remove_method = container.remove + + def get_window_extent(self, *args, **kwargs): + """ + get the axes bounding box in display space; *args* and + *kwargs* are empty + """ + return self.bbox + + def _init_axis(self): + "move this out of __init__ because non-separable axes don't use it" + self.xaxis = maxis.XAxis(self) + self.spines['bottom'].register_axis(self.xaxis) + self.spines['top'].register_axis(self.xaxis) + self.yaxis = maxis.YAxis(self) + self.spines['left'].register_axis(self.yaxis) + self.spines['right'].register_axis(self.yaxis) + self._update_transScale() + + def set_figure(self, fig): + """ + Set the class:`~matplotlib.axes.Axes` figure + + accepts a class:`~matplotlib.figure.Figure` instance + """ + martist.Artist.set_figure(self, fig) + + self.bbox = mtransforms.TransformedBbox(self._position, + fig.transFigure) + # these will be updated later as data is added + self.dataLim = mtransforms.Bbox.null() + self.viewLim = mtransforms.Bbox.unit() + self.transScale = mtransforms.TransformWrapper( + mtransforms.IdentityTransform()) + + self._set_lim_and_transforms() + + def _set_lim_and_transforms(self): + """ + set the *dataLim* and *viewLim* + :class:`~matplotlib.transforms.Bbox` attributes and the + *transScale*, *transData*, *transLimits* and *transAxes* + transformations. + + .. note:: + + This method is primarily used by rectilinear projections + of the :class:`~matplotlib.axes.Axes` class, and is meant + to be overridden by new kinds of projection axes that need + different transformations and limits. (See + :class:`~matplotlib.projections.polar.PolarAxes` for an + example. + + """ + self.transAxes = mtransforms.BboxTransformTo(self.bbox) + + # Transforms the x and y axis separately by a scale factor. + # It is assumed that this part will have non-linear components + # (e.g., for a log scale). + self.transScale = mtransforms.TransformWrapper( + mtransforms.IdentityTransform()) + + # An affine transformation on the data, generally to limit the + # range of the axes + self.transLimits = mtransforms.BboxTransformFrom( + mtransforms.TransformedBbox(self.viewLim, self.transScale)) + + # The parentheses are important for efficiency here -- they + # group the last two (which are usually affines) separately + # from the first (which, with log-scaling can be non-affine). + self.transData = self.transScale + (self.transLimits + self.transAxes) + + self._xaxis_transform = mtransforms.blended_transform_factory( + self.transData, self.transAxes) + self._yaxis_transform = mtransforms.blended_transform_factory( + self.transAxes, self.transData) + + def get_xaxis_transform(self, which='grid'): + """ + Get the transformation used for drawing x-axis labels, ticks + and gridlines. The x-direction is in data coordinates and the + y-direction is in axis coordinates. + + .. note:: + + This transformation is primarily used by the + :class:`~matplotlib.axis.Axis` class, and is meant to be + overridden by new kinds of projections that may need to + place axis elements in different locations. + + """ + if which == 'grid': + return self._xaxis_transform + elif which == 'tick1': + # for cartesian projection, this is bottom spine + return self.spines['bottom'].get_spine_transform() + elif which == 'tick2': + # for cartesian projection, this is top spine + return self.spines['top'].get_spine_transform() + else: + raise ValueError('unknown value for which') + + def get_xaxis_text1_transform(self, pad_points): + """ + Get the transformation used for drawing x-axis labels, which + will add the given amount of padding (in points) between the + axes and the label. The x-direction is in data coordinates + and the y-direction is in axis coordinates. Returns a + 3-tuple of the form:: + + (transform, valign, halign) + + where *valign* and *halign* are requested alignments for the + text. + + .. note:: + + This transformation is primarily used by the + :class:`~matplotlib.axis.Axis` class, and is meant to be + overridden by new kinds of projections that may need to + place axis elements in different locations. + + """ + return (self.get_xaxis_transform(which='tick1') + + mtransforms.ScaledTranslation(0, -1 * pad_points / 72.0, + self.figure.dpi_scale_trans), + "top", "center") + + def get_xaxis_text2_transform(self, pad_points): + """ + Get the transformation used for drawing the secondary x-axis + labels, which will add the given amount of padding (in points) + between the axes and the label. The x-direction is in data + coordinates and the y-direction is in axis coordinates. + Returns a 3-tuple of the form:: + + (transform, valign, halign) + + where *valign* and *halign* are requested alignments for the + text. + + .. note:: + + This transformation is primarily used by the + :class:`~matplotlib.axis.Axis` class, and is meant to be + overridden by new kinds of projections that may need to + place axis elements in different locations. + + """ + return (self.get_xaxis_transform(which='tick2') + + mtransforms.ScaledTranslation(0, pad_points / 72.0, + self.figure.dpi_scale_trans), + "bottom", "center") + + def get_yaxis_transform(self, which='grid'): + """ + Get the transformation used for drawing y-axis labels, ticks + and gridlines. The x-direction is in axis coordinates and the + y-direction is in data coordinates. + + .. note:: + + This transformation is primarily used by the + :class:`~matplotlib.axis.Axis` class, and is meant to be + overridden by new kinds of projections that may need to + place axis elements in different locations. + + """ + if which == 'grid': + return self._yaxis_transform + elif which == 'tick1': + # for cartesian projection, this is bottom spine + return self.spines['left'].get_spine_transform() + elif which == 'tick2': + # for cartesian projection, this is top spine + return self.spines['right'].get_spine_transform() + else: + raise ValueError('unknown value for which') + + def get_yaxis_text1_transform(self, pad_points): + """ + Get the transformation used for drawing y-axis labels, which + will add the given amount of padding (in points) between the + axes and the label. The x-direction is in axis coordinates + and the y-direction is in data coordinates. Returns a 3-tuple + of the form:: + + (transform, valign, halign) + + where *valign* and *halign* are requested alignments for the + text. + + .. note:: + + This transformation is primarily used by the + :class:`~matplotlib.axis.Axis` class, and is meant to be + overridden by new kinds of projections that may need to + place axis elements in different locations. + + """ + return (self.get_yaxis_transform(which='tick1') + + mtransforms.ScaledTranslation(-1 * pad_points / 72.0, 0, + self.figure.dpi_scale_trans), + "center", "right") + + def get_yaxis_text2_transform(self, pad_points): + """ + Get the transformation used for drawing the secondary y-axis + labels, which will add the given amount of padding (in points) + between the axes and the label. The x-direction is in axis + coordinates and the y-direction is in data coordinates. + Returns a 3-tuple of the form:: + + (transform, valign, halign) + + where *valign* and *halign* are requested alignments for the + text. + + .. note:: + + This transformation is primarily used by the + :class:`~matplotlib.axis.Axis` class, and is meant to be + overridden by new kinds of projections that may need to + place axis elements in different locations. + + """ + return (self.get_yaxis_transform(which='tick2') + + mtransforms.ScaledTranslation(pad_points / 72.0, 0, + self.figure.dpi_scale_trans), + "center", "left") + + def _update_transScale(self): + self.transScale.set( + mtransforms.blended_transform_factory( + self.xaxis.get_transform(), self.yaxis.get_transform())) + if hasattr(self, "lines"): + for line in self.lines: + try: + line._transformed_path.invalidate() + except AttributeError: + pass + + def get_position(self, original=False): + 'Return the a copy of the axes rectangle as a Bbox' + if original: + return self._originalPosition.frozen() + else: + return self._position.frozen() + + def set_position(self, pos, which='both'): + """ + Set the axes position with:: + + pos = [left, bottom, width, height] + + in relative 0,1 coords, or *pos* can be a + :class:`~matplotlib.transforms.Bbox` + + There are two position variables: one which is ultimately + used, but which may be modified by :meth:`apply_aspect`, and a + second which is the starting point for :meth:`apply_aspect`. + + + Optional keyword arguments: + *which* + + ========== ==================== + value description + ========== ==================== + 'active' to change the first + 'original' to change the second + 'both' to change both + ========== ==================== + + """ + if not isinstance(pos, mtransforms.BboxBase): + pos = mtransforms.Bbox.from_bounds(*pos) + if which in ('both', 'active'): + self._position.set(pos) + if which in ('both', 'original'): + self._originalPosition.set(pos) + + def reset_position(self): + """Make the original position the active position""" + pos = self.get_position(original=True) + self.set_position(pos, which='active') + + def set_axes_locator(self, locator): + """ + set axes_locator + + ACCEPT: a callable object which takes an axes instance and renderer and + returns a bbox. + """ + self._axes_locator = locator + + def get_axes_locator(self): + """ + return axes_locator + """ + return self._axes_locator + + def _set_artist_props(self, a): + """set the boilerplate props for artists added to axes""" + a.set_figure(self.figure) + if not a.is_transform_set(): + a.set_transform(self.transData) + + a.set_axes(self) + + 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. + + """ + return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0) + + def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'): + """ + Returns a dict whose keys are spine names and values are + Line2D or Patch instances. Each element is used to draw a + spine of the axes. + + In the standard axes, this is a single line segment, but in + other projections it may not be. + + .. note:: + + Intended to be overridden by new projection types. + + """ + return { + 'left': mspines.Spine.linear_spine(self, 'left'), + 'right': mspines.Spine.linear_spine(self, 'right'), + 'bottom': mspines.Spine.linear_spine(self, 'bottom'), + 'top': mspines.Spine.linear_spine(self, 'top'), } + + def cla(self): + """Clear the current axes.""" + # Note: this is called by Axes.__init__() + self.xaxis.cla() + self.yaxis.cla() + for name, spine in self.spines.iteritems(): + spine.cla() + + self.ignore_existing_data_limits = True + self.callbacks = cbook.CallbackRegistry() + + if self._sharex is not None: + # major and minor are class instances with + # locator and formatter attributes + self.xaxis.major = self._sharex.xaxis.major + self.xaxis.minor = self._sharex.xaxis.minor + x0, x1 = self._sharex.get_xlim() + self.set_xlim(x0, x1, emit=False, auto=None) + + # Save the current formatter/locator so we don't lose it + majf = self._sharex.xaxis.get_major_formatter() + minf = self._sharex.xaxis.get_minor_formatter() + majl = self._sharex.xaxis.get_major_locator() + minl = self._sharex.xaxis.get_minor_locator() + + # This overwrites the current formatter/locator + self.xaxis._set_scale(self._sharex.xaxis.get_scale()) + + # Reset the formatter/locator + self.xaxis.set_major_formatter(majf) + self.xaxis.set_minor_formatter(minf) + self.xaxis.set_major_locator(majl) + self.xaxis.set_minor_locator(minl) + else: + self.xaxis._set_scale('linear') + + if self._sharey is not None: + self.yaxis.major = self._sharey.yaxis.major + self.yaxis.minor = self._sharey.yaxis.minor + y0, y1 = self._sharey.get_ylim() + self.set_ylim(y0, y1, emit=False, auto=None) + + # Save the current formatter/locator so we don't lose it + majf = self._sharey.yaxis.get_major_formatter() + minf = self._sharey.yaxis.get_minor_formatter() + majl = self._sharey.yaxis.get_major_locator() + minl = self._sharey.yaxis.get_minor_locator() + + # This overwrites the current formatter/locator + self.yaxis._set_scale(self._sharey.yaxis.get_scale()) + + # Reset the formatter/locator + self.yaxis.set_major_formatter(majf) + self.yaxis.set_minor_formatter(minf) + self.yaxis.set_major_locator(majl) + self.yaxis.set_minor_locator(minl) + else: + self.yaxis._set_scale('linear') + + self._autoscaleXon = True + self._autoscaleYon = True + self._xmargin = rcParams['axes.xmargin'] + self._ymargin = rcParams['axes.ymargin'] + self._tight = False + self._update_transScale() # needed? + + self._get_lines = _process_plot_var_args(self) + self._get_patches_for_fill = _process_plot_var_args(self, 'fill') + + self._gridOn = rcParams['axes.grid'] + self.lines = [] + self.patches = [] + self.texts = [] + self.tables = [] + self.artists = [] + self.images = [] + self._current_image = None # strictly for pyplot via _sci, _gci + self.legend_ = None + self.collections = [] # collection.Collection instances + self.containers = [] + + self.grid(self._gridOn) + props = font_manager.FontProperties(size=rcParams['axes.titlesize']) + + self.titleOffsetTrans = mtransforms.ScaledTranslation( + 0.0, 5.0 / 72.0, self.figure.dpi_scale_trans) + self.title = mtext.Text( + x=0.5, y=1.0, text='', + fontproperties=props, + verticalalignment='baseline', + horizontalalignment='center', + ) + self._left_title = mtext.Text( + x=0.0, y=1.0, text='', + fontproperties=props, + verticalalignment='baseline', + horizontalalignment='left', ) + self._right_title = mtext.Text( + x=1.0, y=1.0, text='', + fontproperties=props, + verticalalignment='baseline', + horizontalalignment='right', + ) + + for _title in (self.title, self._left_title, self._right_title): + _title.set_transform(self.transAxes + self.titleOffsetTrans) + _title.set_clip_box(None) + self._set_artist_props(_title) + + # the patch draws the background of the axes. we want this to + # be below the other artists; the axesPatch name is + # deprecated. We use the frame to draw the edges so we are + # setting the edgecolor to None + self.patch = self.axesPatch = self._gen_axes_patch() + self.patch.set_figure(self.figure) + self.patch.set_facecolor(self._axisbg) + self.patch.set_edgecolor('None') + self.patch.set_linewidth(0) + self.patch.set_transform(self.transAxes) + + self.axison = True + + self.xaxis.set_clip_path(self.patch) + self.yaxis.set_clip_path(self.patch) + + self._shared_x_axes.clean() + self._shared_y_axes.clean() + + def clear(self): + """clear the axes""" + self.cla() + + def set_color_cycle(self, clist): + """ + Set the color cycle for any future plot commands on this Axes. + + *clist* is a list of mpl color specifiers. + """ + self._get_lines.set_color_cycle(clist) + self._get_patches_for_fill.set_color_cycle(clist) + + def ishold(self): + """return the HOLD status of the axes""" + return self._hold + + def hold(self, b=None): + """ + Call signature:: + + hold(b=None) + + Set the hold state. If *hold* is *None* (default), toggle the + *hold* state. Else set the *hold* state to boolean value *b*. + + Examples:: + + # toggle hold + hold() + + # turn hold on + hold(True) + + # turn hold off + hold(False) + + When hold is *True*, subsequent plot commands will be added to + the current axes. When hold is *False*, the current axes and + figure will be cleared on the next plot command + + """ + if b is None: + self._hold = not self._hold + else: + self._hold = b + + def get_aspect(self): + return self._aspect + + def set_aspect(self, aspect, adjustable=None, anchor=None): + """ + *aspect* + + ======== ================================================ + value description + ======== ================================================ + 'auto' automatic; fill position rectangle with data + 'normal' same as 'auto'; deprecated + 'equal' same scaling from data to plot units for x and y + num a circle will be stretched such that the height + is num times the width. aspect=1 is the same as + aspect='equal'. + ======== ================================================ + + *adjustable* + + ============ ===================================== + value description + ============ ===================================== + 'box' change physical size of axes + 'datalim' change xlim or ylim + 'box-forced' same as 'box', but axes can be shared + ============ ===================================== + + 'box' does not allow axes sharing, as this can cause + unintended side effect. For cases when sharing axes is + fine, use 'box-forced'. + + *anchor* + + ===== ===================== + value description + ===== ===================== + 'C' centered + 'SW' lower left corner + 'S' middle of bottom edge + 'SE' lower right corner + etc. + ===== ===================== + + .. deprecated:: 1.2 + the option 'normal' for aspect is deprecated. Use 'auto' instead. + """ + if aspect == 'normal': + cbook.warn_deprecated( + '1.2', name='normal', alternative='auto', obj_type='aspect') + self._aspect = 'auto' + + elif aspect in ('equal', 'auto'): + self._aspect = aspect + else: + self._aspect = float(aspect) # raise ValueError if necessary + + if adjustable is not None: + self.set_adjustable(adjustable) + if anchor is not None: + self.set_anchor(anchor) + + def get_adjustable(self): + return self._adjustable + + def set_adjustable(self, adjustable): + """ + ACCEPTS: [ 'box' | 'datalim' | 'box-forced'] + """ + if adjustable in ('box', 'datalim', 'box-forced'): + if self in self._shared_x_axes or self in self._shared_y_axes: + if adjustable == 'box': + raise ValueError( + 'adjustable must be "datalim" for shared axes') + self._adjustable = adjustable + else: + raise ValueError('argument must be "box", or "datalim"') + + def get_anchor(self): + return self._anchor + + def set_anchor(self, anchor): + """ + *anchor* + + ===== ============ + value description + ===== ============ + 'C' Center + 'SW' bottom left + 'S' bottom + 'SE' bottom right + 'E' right + 'NE' top right + 'N' top + 'NW' top left + 'W' left + ===== ============ + + """ + if anchor in mtransforms.Bbox.coefs.keys() or len(anchor) == 2: + self._anchor = anchor + else: + raise ValueError('argument must be among %s' % + ', '.join(mtransforms.Bbox.coefs.keys())) + + def get_data_ratio(self): + """ + Returns the aspect ratio of the raw data. + + This method is intended to be overridden by new projection + types. + """ + xmin, xmax = self.get_xbound() + ymin, ymax = self.get_ybound() + + xsize = max(math.fabs(xmax - xmin), 1e-30) + ysize = max(math.fabs(ymax - ymin), 1e-30) + + return ysize / xsize + + def get_data_ratio_log(self): + """ + Returns the aspect ratio of the raw data in log scale. + Will be used when both axis scales are in log. + """ + xmin, xmax = self.get_xbound() + ymin, ymax = self.get_ybound() + + xsize = max(math.fabs(math.log10(xmax) - math.log10(xmin)), 1e-30) + ysize = max(math.fabs(math.log10(ymax) - math.log10(ymin)), 1e-30) + + return ysize / xsize + + def apply_aspect(self, position=None): + """ + Use :meth:`_aspect` and :meth:`_adjustable` to modify the + axes box or the view limits. + """ + if position is None: + position = self.get_position(original=True) + + aspect = self.get_aspect() + + if self.name != 'polar': + xscale, yscale = self.get_xscale(), self.get_yscale() + if xscale == "linear" and yscale == "linear": + aspect_scale_mode = "linear" + elif xscale == "log" and yscale == "log": + aspect_scale_mode = "log" + elif ((xscale == "linear" and yscale == "log") or + (xscale == "log" and yscale == "linear")): + if aspect is not "auto": + warnings.warn( + 'aspect is not supported for Axes with xscale=%s, ' + 'yscale=%s' % (xscale, yscale)) + aspect = "auto" + else: # some custom projections have their own scales. + pass + else: + aspect_scale_mode = "linear" + + if aspect == 'auto': + self.set_position(position, which='active') + return + + if aspect == 'equal': + A = 1 + else: + A = aspect + + #Ensure at drawing time that any Axes involved in axis-sharing + # does not have its position changed. + if self in self._shared_x_axes or self in self._shared_y_axes: + if self._adjustable == 'box': + self._adjustable = 'datalim' + warnings.warn( + 'shared axes: "adjustable" is being changed to "datalim"') + + figW, figH = self.get_figure().get_size_inches() + fig_aspect = figH / figW + if self._adjustable in ['box', 'box-forced']: + if aspect_scale_mode == "log": + box_aspect = A * self.get_data_ratio_log() + else: + box_aspect = A * self.get_data_ratio() + pb = position.frozen() + pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect) + self.set_position(pb1.anchored(self.get_anchor(), pb), 'active') + return + + # reset active to original in case it had been changed + # by prior use of 'box' + self.set_position(position, which='active') + + xmin, xmax = self.get_xbound() + ymin, ymax = self.get_ybound() + + if aspect_scale_mode == "log": + xmin, xmax = math.log10(xmin), math.log10(xmax) + ymin, ymax = math.log10(ymin), math.log10(ymax) + + xsize = max(math.fabs(xmax - xmin), 1e-30) + ysize = max(math.fabs(ymax - ymin), 1e-30) + + l, b, w, h = position.bounds + box_aspect = fig_aspect * (h / w) + data_ratio = box_aspect / A + + y_expander = (data_ratio * xsize / ysize - 1.0) + #print 'y_expander', y_expander + # If y_expander > 0, the dy/dx viewLim ratio needs to increase + if abs(y_expander) < 0.005: + #print 'good enough already' + return + + if aspect_scale_mode == "log": + dL = self.dataLim + dL_width = math.log10(dL.x1) - math.log10(dL.x0) + dL_height = math.log10(dL.y1) - math.log10(dL.y0) + xr = 1.05 * dL_width + yr = 1.05 * dL_height + else: + dL = self.dataLim + xr = 1.05 * dL.width + yr = 1.05 * dL.height + + xmarg = xsize - xr + ymarg = ysize - yr + Ysize = data_ratio * xsize + Xsize = ysize / data_ratio + Xmarg = Xsize - xr + Ymarg = Ysize - yr + xm = 0 # Setting these targets to, e.g., 0.05*xr does not seem to + # help. + ym = 0 + #print 'xmin, xmax, ymin, ymax', xmin, xmax, ymin, ymax + #print 'xsize, Xsize, ysize, Ysize', xsize, Xsize, ysize, Ysize + + changex = (self in self._shared_y_axes + and self not in self._shared_x_axes) + changey = (self in self._shared_x_axes + and self not in self._shared_y_axes) + if changex and changey: + warnings.warn("adjustable='datalim' cannot work with shared " + "x and y axes") + return + if changex: + adjust_y = False + else: + #print 'xmarg, ymarg, Xmarg, Ymarg', xmarg, ymarg, Xmarg, Ymarg + if xmarg > xm and ymarg > ym: + adjy = ((Ymarg > 0 and y_expander < 0) + or (Xmarg < 0 and y_expander > 0)) + else: + adjy = y_expander > 0 + #print 'y_expander, adjy', y_expander, adjy + adjust_y = changey or adjy # (Ymarg > xmarg) + if adjust_y: + yc = 0.5 * (ymin + ymax) + y0 = yc - Ysize / 2.0 + y1 = yc + Ysize / 2.0 + if aspect_scale_mode == "log": + self.set_ybound((10. ** y0, 10. ** y1)) + else: + self.set_ybound((y0, y1)) + #print 'New y0, y1:', y0, y1 + #print 'New ysize, ysize/xsize', y1-y0, (y1-y0)/xsize + else: + xc = 0.5 * (xmin + xmax) + x0 = xc - Xsize / 2.0 + x1 = xc + Xsize / 2.0 + if aspect_scale_mode == "log": + self.set_xbound((10. ** x0, 10. ** x1)) + else: + self.set_xbound((x0, x1)) + #print 'New x0, x1:', x0, x1 + #print 'New xsize, ysize/xsize', x1-x0, ysize/(x1-x0) + + def axis(self, *v, **kwargs): + """ + Convenience method for manipulating the x and y view limits + and the aspect ratio of the plot. For details, see + :func:`~matplotlib.pyplot.axis`. + + *kwargs* are passed on to :meth:`set_xlim` and + :meth:`set_ylim` + """ + if len(v) == 0 and len(kwargs) == 0: + xmin, xmax = self.get_xlim() + ymin, ymax = self.get_ylim() + return xmin, xmax, ymin, ymax + + if len(v) == 1 and is_string_like(v[0]): + s = v[0].lower() + if s == 'on': + self.set_axis_on() + elif s == 'off': + self.set_axis_off() + elif s in ('equal', 'tight', 'scaled', 'normal', 'auto', 'image'): + self.set_autoscale_on(True) + self.set_aspect('auto') + self.autoscale_view(tight=False) + # self.apply_aspect() + if s == 'equal': + self.set_aspect('equal', adjustable='datalim') + elif s == 'scaled': + self.set_aspect('equal', adjustable='box', anchor='C') + self.set_autoscale_on(False) # Req. by Mark Bakker + elif s == 'tight': + self.autoscale_view(tight=True) + self.set_autoscale_on(False) + elif s == 'image': + self.autoscale_view(tight=True) + self.set_autoscale_on(False) + self.set_aspect('equal', adjustable='box', anchor='C') + + else: + raise ValueError('Unrecognized string %s to axis; ' + 'try on or off' % s) + xmin, xmax = self.get_xlim() + ymin, ymax = self.get_ylim() + return xmin, xmax, ymin, ymax + + emit = kwargs.get('emit', True) + try: + v[0] + except IndexError: + xmin = kwargs.get('xmin', None) + xmax = kwargs.get('xmax', None) + auto = False # turn off autoscaling, unless... + if xmin is None and xmax is None: + auto = None # leave autoscaling state alone + xmin, xmax = self.set_xlim(xmin, xmax, emit=emit, auto=auto) + + ymin = kwargs.get('ymin', None) + ymax = kwargs.get('ymax', None) + auto = False # turn off autoscaling, unless... + if ymin is None and ymax is None: + auto = None # leave autoscaling state alone + ymin, ymax = self.set_ylim(ymin, ymax, emit=emit, auto=auto) + return xmin, xmax, ymin, ymax + + v = v[0] + if len(v) != 4: + raise ValueError('v must contain [xmin xmax ymin ymax]') + + self.set_xlim([v[0], v[1]], emit=emit, auto=False) + self.set_ylim([v[2], v[3]], emit=emit, auto=False) + + return v + + def get_legend(self): + """ + Return the legend.Legend instance, or None if no legend is defined + """ + return self.legend_ + + def get_images(self): + """return a list of Axes images contained by the Axes""" + return cbook.silent_list('AxesImage', self.images) + + def get_lines(self): + """Return a list of lines contained by the Axes""" + return cbook.silent_list('Line2D', self.lines) + + def get_xaxis(self): + """Return the XAxis instance""" + return self.xaxis + + def get_xgridlines(self): + """Get the x grid lines as a list of Line2D instances""" + return cbook.silent_list('Line2D xgridline', + self.xaxis.get_gridlines()) + + def get_xticklines(self): + """Get the xtick lines as a list of Line2D instances""" + return cbook.silent_list('Text xtickline', + self.xaxis.get_ticklines()) + + def get_yaxis(self): + """Return the YAxis instance""" + return self.yaxis + + def get_ygridlines(self): + """Get the y grid lines as a list of Line2D instances""" + return cbook.silent_list('Line2D ygridline', + self.yaxis.get_gridlines()) + + def get_yticklines(self): + """Get the ytick lines as a list of Line2D instances""" + return cbook.silent_list('Line2D ytickline', + self.yaxis.get_ticklines()) + + #### Adding and tracking artists + + def _sci(self, im): + """ + helper for :func:`~matplotlib.pyplot.sci`; + do not use elsewhere. + """ + if isinstance(im, matplotlib.contour.ContourSet): + if im.collections[0] not in self.collections: + raise ValueError( + "ContourSet must be in current Axes") + elif im not in self.images and im not in self.collections: + raise ValueError( + "Argument must be an image, collection, or ContourSet in " + "this Axes") + self._current_image = im + + def _gci(self): + """ + Helper for :func:`~matplotlib.pyplot.gci`; + do not use elsewhere. + """ + return self._current_image + + def has_data(self): + """ + Return *True* if any artists have been added to axes. + + This should not be used to determine whether the *dataLim* + need to be updated, and may not actually be useful for + anything. + """ + return ( + len(self.collections) + + len(self.images) + + len(self.lines) + + len(self.patches)) > 0 + + def add_artist(self, a): + """ + Add any :class:`~matplotlib.artist.Artist` to the axes. + + Returns the artist. + """ + a.set_axes(self) + self.artists.append(a) + self._set_artist_props(a) + a.set_clip_path(self.patch) + a._remove_method = lambda h: self.artists.remove(h) + return a + + def add_collection(self, collection, autolim=True): + """ + Add a :class:`~matplotlib.collections.Collection` instance + to the axes. + + Returns the collection. + """ + label = collection.get_label() + if not label: + collection.set_label('_collection%d' % len(self.collections)) + self.collections.append(collection) + self._set_artist_props(collection) + + if collection.get_clip_path() is None: + collection.set_clip_path(self.patch) + + if (autolim and + collection._paths is not None and + len(collection._paths) and + len(collection._offsets)): + self.update_datalim(collection.get_datalim(self.transData)) + + collection._remove_method = lambda h: self.collections.remove(h) + return collection + + def add_line(self, line): + """ + Add a :class:`~matplotlib.lines.Line2D` to the list of plot + lines + + Returns the line. + """ + self._set_artist_props(line) + if line.get_clip_path() is None: + line.set_clip_path(self.patch) + + self._update_line_limits(line) + if not line.get_label(): + line.set_label('_line%d' % len(self.lines)) + self.lines.append(line) + line._remove_method = lambda h: self.lines.remove(h) + return line + + def _update_line_limits(self, line): + """ + Figures out the data limit of the given line, updating self.dataLim. + """ + path = line.get_path() + if path.vertices.size == 0: + return + + line_trans = line.get_transform() + + if line_trans == self.transData: + data_path = path + + elif any(line_trans.contains_branch_seperately(self.transData)): + # identify the transform to go from line's coordinates + # to data coordinates + trans_to_data = line_trans - self.transData + + # if transData is affine we can use the cached non-affine component + # of line's path. (since the non-affine part of line_trans is + # entirely encapsulated in trans_to_data). + if self.transData.is_affine: + line_trans_path = line._get_transformed_path() + na_path, _ = line_trans_path.get_transformed_path_and_affine() + data_path = trans_to_data.transform_path_affine(na_path) + else: + data_path = trans_to_data.transform_path(path) + else: + # for backwards compatibility we update the dataLim with the + # coordinate range of the given path, even though the coordinate + # systems are completely different. This may occur in situations + # such as when ax.transAxes is passed through for absolute + # positioning. + data_path = path + + if data_path.vertices.size > 0: + updatex, updatey = line_trans.contains_branch_seperately( + self.transData) + self.dataLim.update_from_path(data_path, + self.ignore_existing_data_limits, + updatex=updatex, + updatey=updatey) + self.ignore_existing_data_limits = False + + def add_patch(self, p): + """ + Add a :class:`~matplotlib.patches.Patch` *p* to the list of + axes patches; the clipbox will be set to the Axes clipping + box. If the transform is not set, it will be set to + :attr:`transData`. + + Returns the patch. + """ + + self._set_artist_props(p) + if p.get_clip_path() is None: + p.set_clip_path(self.patch) + self._update_patch_limits(p) + self.patches.append(p) + p._remove_method = lambda h: self.patches.remove(h) + return p + + def _update_patch_limits(self, patch): + """update the data limits for patch *p*""" + # hist can add zero height Rectangles, which is useful to keep + # the bins, counts and patches lined up, but it throws off log + # scaling. We'll ignore rects with zero height or width in + # the auto-scaling + + # cannot check for '==0' since unitized data may not compare to zero + if (isinstance(patch, mpatches.Rectangle) and + ((not patch.get_width()) or (not patch.get_height()))): + return + vertices = patch.get_path().vertices + if vertices.size > 0: + xys = patch.get_patch_transform().transform(vertices) + if patch.get_data_transform() != self.transData: + patch_to_data = (patch.get_data_transform() - + self.transData) + xys = patch_to_data.transform(xys) + + updatex, updatey = patch.get_transform().\ + contains_branch_seperately(self.transData) + self.update_datalim(xys, updatex=updatex, + updatey=updatey) + + def add_table(self, tab): + """ + Add a :class:`~matplotlib.tables.Table` instance to the + list of axes tables + + Returns the table. + """ + self._set_artist_props(tab) + self.tables.append(tab) + tab.set_clip_path(self.patch) + tab._remove_method = lambda h: self.tables.remove(h) + return tab + + def add_container(self, container): + """ + Add a :class:`~matplotlib.container.Container` instance + to the axes. + + Returns the collection. + """ + label = container.get_label() + if not label: + container.set_label('_container%d' % len(self.containers)) + self.containers.append(container) + container.set_remove_method(lambda h: self.containers.remove(h)) + return container + + def relim(self): + """ + Recompute the data limits based on current artists. + + At present, :class:`~matplotlib.collections.Collection` + instances are not supported. + """ + # Collections are deliberately not supported (yet); see + # the TODO note in artists.py. + self.dataLim.ignore(True) + self.dataLim.set_points(mtransforms.Bbox.null().get_points()) + self.ignore_existing_data_limits = True + + for line in self.lines: + self._update_line_limits(line) + + for p in self.patches: + self._update_patch_limits(p) + + def update_datalim(self, xys, updatex=True, updatey=True): + """ + Update the data lim bbox with seq of xy tups or equiv. 2-D array + """ + # if no data is set currently, the bbox will ignore its + # limits and set the bound to be the bounds of the xydata. + # Otherwise, it will compute the bounds of it's current data + # and the data in xydata + + if iterable(xys) and not len(xys): + return + if not ma.isMaskedArray(xys): + xys = np.asarray(xys) + self.dataLim.update_from_data_xy(xys, self.ignore_existing_data_limits, + updatex=updatex, updatey=updatey) + self.ignore_existing_data_limits = False + + def update_datalim_numerix(self, x, y): + """ + Update the data lim bbox with seq of xy tups + """ + # if no data is set currently, the bbox will ignore it's + # limits and set the bound to be the bounds of the xydata. + # Otherwise, it will compute the bounds of it's current data + # and the data in xydata + if iterable(x) and not len(x): + return + self.dataLim.update_from_data(x, y, self.ignore_existing_data_limits) + self.ignore_existing_data_limits = False + + def update_datalim_bounds(self, bounds): + """ + Update the datalim to include the given + :class:`~matplotlib.transforms.Bbox` *bounds* + """ + self.dataLim.set(mtransforms.Bbox.union([self.dataLim, bounds])) + + def _process_unit_info(self, xdata=None, ydata=None, kwargs=None): + """Look for unit *kwargs* and update the axis instances as necessary""" + + if self.xaxis is None or self.yaxis is None: + return + + #print 'processing', self.get_geometry() + if xdata is not None: + # we only need to update if there is nothing set yet. + if not self.xaxis.have_units(): + self.xaxis.update_units(xdata) + #print '\tset from xdata', self.xaxis.units + + if ydata is not None: + # we only need to update if there is nothing set yet. + if not self.yaxis.have_units(): + self.yaxis.update_units(ydata) + #print '\tset from ydata', self.yaxis.units + + # process kwargs 2nd since these will override default units + if kwargs is not None: + xunits = kwargs.pop('xunits', self.xaxis.units) + if self.name == 'polar': + xunits = kwargs.pop('thetaunits', xunits) + if xunits != self.xaxis.units: + #print '\tkw setting xunits', xunits + self.xaxis.set_units(xunits) + # If the units being set imply a different converter, + # we need to update. + if xdata is not None: + self.xaxis.update_units(xdata) + + yunits = kwargs.pop('yunits', self.yaxis.units) + if self.name == 'polar': + yunits = kwargs.pop('runits', yunits) + if yunits != self.yaxis.units: + #print '\tkw setting yunits', yunits + self.yaxis.set_units(yunits) + # If the units being set imply a different converter, + # we need to update. + if ydata is not None: + self.yaxis.update_units(ydata) + + def in_axes(self, mouseevent): + """ + Return *True* if the given *mouseevent* (in display coords) + is in the Axes + """ + return self.patch.contains(mouseevent)[0] + + def get_autoscale_on(self): + """ + Get whether autoscaling is applied for both axes on plot commands + """ + return self._autoscaleXon and self._autoscaleYon + + def get_autoscalex_on(self): + """ + Get whether autoscaling for the x-axis is applied on plot commands + """ + return self._autoscaleXon + + def get_autoscaley_on(self): + """ + Get whether autoscaling for the y-axis is applied on plot commands + """ + return self._autoscaleYon + + def set_autoscale_on(self, b): + """ + Set whether autoscaling is applied on plot commands + + accepts: [ *True* | *False* ] + """ + self._autoscaleXon = b + self._autoscaleYon = b + + def set_autoscalex_on(self, b): + """ + Set whether autoscaling for the x-axis is applied on plot commands + + accepts: [ *True* | *False* ] + """ + self._autoscaleXon = b + + def set_autoscaley_on(self, b): + """ + Set whether autoscaling for the y-axis is applied on plot commands + + accepts: [ *True* | *False* ] + """ + self._autoscaleYon = b + + def set_xmargin(self, m): + """ + Set padding of X data limits prior to autoscaling. + + *m* times the data interval will be added to each + end of that interval before it is used in autoscaling. + + accepts: float in range 0 to 1 + """ + if m < 0 or m > 1: + raise ValueError("margin must be in range 0 to 1") + self._xmargin = m + + def set_ymargin(self, m): + """ + Set padding of Y data limits prior to autoscaling. + + *m* times the data interval will be added to each + end of that interval before it is used in autoscaling. + + accepts: float in range 0 to 1 + """ + if m < 0 or m > 1: + raise ValueError("margin must be in range 0 to 1") + self._ymargin = m + + def margins(self, *args, **kw): + """ + Set or retrieve autoscaling margins. + + signatures:: + + margins() + + returns xmargin, ymargin + + :: + + margins(margin) + + margins(xmargin, ymargin) + + margins(x=xmargin, y=ymargin) + + margins(..., tight=False) + + All three forms above set the xmargin and ymargin parameters. + All keyword parameters are optional. A single argument + specifies both xmargin and ymargin. The *tight* parameter + is passed to :meth:`autoscale_view`, which is executed after + a margin is changed; the default here is *True*, on the + assumption that when margins are specified, no additional + padding to match tick marks is usually desired. Setting + *tight* to *None* will preserve the previous setting. + + Specifying any margin changes only the autoscaling; for example, + if *xmargin* is not None, then *xmargin* times the X data + interval will be added to each end of that interval before + it is used in autoscaling. + + """ + if not args and not kw: + return self._xmargin, self._ymargin + + tight = kw.pop('tight', True) + mx = kw.pop('x', None) + my = kw.pop('y', None) + if len(args) == 1: + mx = my = args[0] + elif len(args) == 2: + mx, my = args + else: + raise ValueError("more than two arguments were supplied") + if mx is not None: + self.set_xmargin(mx) + if my is not None: + self.set_ymargin(my) + + scalex = (mx is not None) + scaley = (my is not None) + + self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley) + + def set_rasterization_zorder(self, z): + """ + Set zorder value below which artists will be rasterized. Set + to `None` to disable rasterizing of artists below a particular + zorder. + """ + self._rasterization_zorder = z + + def get_rasterization_zorder(self): + """ + Get zorder value below which artists will be rasterized + """ + return self._rasterization_zorder + + def autoscale(self, enable=True, axis='both', tight=None): + """ + Autoscale the axis view to the data (toggle). + + Convenience method for simple axis view autoscaling. + It turns autoscaling on or off, and then, + if autoscaling for either axis is on, it performs + the autoscaling on the specified axis or axes. + + *enable*: [True | False | None] + True (default) turns autoscaling on, False turns it off. + None leaves the autoscaling state unchanged. + + *axis*: ['x' | 'y' | 'both'] + which axis to operate on; default is 'both' + + *tight*: [True | False | None] + If True, set view limits to data limits; + if False, let the locator and margins expand the view limits; + if None, use tight scaling if the only artist is an image, + otherwise treat *tight* as False. + The *tight* setting is retained for future autoscaling + until it is explicitly changed. + + + Returns None. + """ + if enable is None: + scalex = True + scaley = True + else: + scalex = False + scaley = False + if axis in ['x', 'both']: + self._autoscaleXon = bool(enable) + scalex = self._autoscaleXon + if axis in ['y', 'both']: + self._autoscaleYon = bool(enable) + scaley = self._autoscaleYon + self.autoscale_view(tight=tight, scalex=scalex, scaley=scaley) + + def autoscale_view(self, tight=None, scalex=True, scaley=True): + """ + Autoscale the view limits using the data limits. You can + selectively autoscale only a single axis, eg, the xaxis by + setting *scaley* to *False*. The autoscaling preserves any + axis direction reversal that has already been done. + + The data limits are not updated automatically when artist data are + changed after the artist has been added to an Axes instance. In that + case, use :meth:`matplotlib.axes.Axes.relim` prior to calling + autoscale_view. + """ + if tight is None: + # if image data only just use the datalim + _tight = self._tight or (len(self.images) > 0 and + len(self.lines) == 0 and + len(self.patches) == 0) + else: + _tight = self._tight = bool(tight) + + if scalex and self._autoscaleXon: + xshared = self._shared_x_axes.get_siblings(self) + dl = [ax.dataLim for ax in xshared] + bb = mtransforms.BboxBase.union(dl) + x0, x1 = bb.intervalx + xlocator = self.xaxis.get_major_locator() + try: + # e.g., DateLocator has its own nonsingular() + x0, x1 = xlocator.nonsingular(x0, x1) + except AttributeError: + # Default nonsingular for, e.g., MaxNLocator + x0, x1 = mtransforms.nonsingular(x0, x1, increasing=False, + expander=0.05) + if self._xmargin > 0: + delta = (x1 - x0) * self._xmargin + x0 -= delta + x1 += delta + if not _tight: + x0, x1 = xlocator.view_limits(x0, x1) + self.set_xbound(x0, x1) + + if scaley and self._autoscaleYon: + yshared = self._shared_y_axes.get_siblings(self) + dl = [ax.dataLim for ax in yshared] + bb = mtransforms.BboxBase.union(dl) + y0, y1 = bb.intervaly + ylocator = self.yaxis.get_major_locator() + try: + y0, y1 = ylocator.nonsingular(y0, y1) + except AttributeError: + y0, y1 = mtransforms.nonsingular(y0, y1, increasing=False, + expander=0.05) + if self._ymargin > 0: + delta = (y1 - y0) * self._ymargin + y0 -= delta + y1 += delta + if not _tight: + y0, y1 = ylocator.view_limits(y0, y1) + self.set_ybound(y0, y1) + + #### Drawing + + @allow_rasterization + def draw(self, renderer=None, inframe=False): + """Draw everything (plot lines, axes, labels)""" + if renderer is None: + renderer = self._cachedRenderer + + if renderer is None: + raise RuntimeError('No renderer defined') + if not self.get_visible(): + return + renderer.open_group('axes') + + locator = self.get_axes_locator() + if locator: + pos = locator(self, renderer) + self.apply_aspect(pos) + else: + self.apply_aspect() + + artists = [] + + artists.extend(self.collections) + artists.extend(self.patches) + artists.extend(self.lines) + artists.extend(self.texts) + artists.extend(self.artists) + if self.axison and not inframe: + if self._axisbelow: + self.xaxis.set_zorder(0.5) + self.yaxis.set_zorder(0.5) + else: + self.xaxis.set_zorder(2.5) + self.yaxis.set_zorder(2.5) + artists.extend([self.xaxis, self.yaxis]) + if not inframe: + artists.append(self.title) + artists.append(self._left_title) + artists.append(self._right_title) + artists.extend(self.tables) + if self.legend_ is not None: + artists.append(self.legend_) + + # the frame draws the edges around the axes patch -- we + # decouple these so the patch can be in the background and the + # frame in the foreground. + if self.axison and self._frameon: + artists.extend(self.spines.itervalues()) + + if self.figure.canvas.is_saving(): + dsu = [(a.zorder, a) for a in artists] + else: + dsu = [(a.zorder, a) for a in artists + if not a.get_animated()] + + # add images to dsu if the backend support compositing. + # otherwise, does the manaul compositing without adding images to dsu. + if len(self.images) <= 1 or renderer.option_image_nocomposite(): + dsu.extend([(im.zorder, im) for im in self.images]) + _do_composite = False + else: + _do_composite = True + + dsu.sort(key=itemgetter(0)) + + # rasterize artists with negative zorder + # if the minimum zorder is negative, start rasterization + rasterization_zorder = self._rasterization_zorder + if (rasterization_zorder is not None and + len(dsu) > 0 and dsu[0][0] < rasterization_zorder): + renderer.start_rasterizing() + dsu_rasterized = [l for l in dsu if l[0] < rasterization_zorder] + dsu = [l for l in dsu if l[0] >= rasterization_zorder] + else: + dsu_rasterized = [] + + # the patch draws the background rectangle -- the frame below + # will draw the edges + if self.axison and self._frameon: + self.patch.draw(renderer) + + if _do_composite: + # make a composite image blending alpha + # list of (mimage.Image, ox, oy) + + zorder_images = [(im.zorder, im) for im in self.images + if im.get_visible()] + zorder_images.sort(key=lambda x: x[0]) + + mag = renderer.get_image_magnification() + ims = [(im.make_image(mag), 0, 0, im.get_alpha()) + for z, im in zorder_images] + + l, b, r, t = self.bbox.extents + width = mag * ((round(r) + 0.5) - (round(l) - 0.5)) + height = mag * ((round(t) + 0.5) - (round(b) - 0.5)) + im = mimage.from_images(height, + width, + ims) + + im.is_grayscale = False + l, b, w, h = self.bbox.bounds + # composite images need special args so they will not + # respect z-order for now + + gc = renderer.new_gc() + gc.set_clip_rectangle(self.bbox) + gc.set_clip_path(mtransforms.TransformedPath( + self.patch.get_path(), + self.patch.get_transform())) + + renderer.draw_image(gc, round(l), round(b), im) + gc.restore() + + if dsu_rasterized: + for zorder, a in dsu_rasterized: + a.draw(renderer) + renderer.stop_rasterizing() + + for zorder, a in dsu: + a.draw(renderer) + + renderer.close_group('axes') + self._cachedRenderer = renderer + + def draw_artist(self, a): + """ + This method can only be used after an initial draw which + caches the renderer. It is used to efficiently update Axes + data (axis ticks, labels, etc are not updated) + """ + assert self._cachedRenderer is not None + a.draw(self._cachedRenderer) + + def redraw_in_frame(self): + """ + This method can only be used after an initial draw which + caches the renderer. It is used to efficiently update Axes + data (axis ticks, labels, etc are not updated) + """ + assert self._cachedRenderer is not None + self.draw(self._cachedRenderer, inframe=True) + + def get_renderer_cache(self): + return self._cachedRenderer + + #### Axes rectangle characteristics + + def get_frame_on(self): + """ + Get whether the axes rectangle patch is drawn + """ + return self._frameon + + def set_frame_on(self, b): + """ + Set whether the axes rectangle patch is drawn + + ACCEPTS: [ *True* | *False* ] + """ + self._frameon = b + + def get_axisbelow(self): + """ + Get whether axis below is true or not + """ + return self._axisbelow + + def set_axisbelow(self, b): + """ + Set whether the axis ticks and gridlines are above or below most + artists + + ACCEPTS: [ *True* | *False* ] + """ + self._axisbelow = b + + @docstring.dedent_interpd + def grid(self, b=None, which='major', axis='both', **kwargs): + """ + Turn the axes grids on or off. + + Call signature:: + + grid(self, b=None, which='major', axis='both', **kwargs) + + Set the axes grids on or off; *b* is a boolean. (For MATLAB + compatibility, *b* may also be a string, 'on' or 'off'.) + + If *b* is *None* and ``len(kwargs)==0``, toggle the grid state. If + *kwargs* are supplied, it is assumed that you want a grid and *b* + is thus set to *True*. + + *which* can be 'major' (default), 'minor', or 'both' to control + whether major tick grids, minor tick grids, or both are affected. + + *axis* can be 'both' (default), 'x', or 'y' to control which + set of gridlines are drawn. + + *kwargs* are used to set the grid line properties, eg:: + + ax.grid(color='r', linestyle='-', linewidth=2) + + Valid :class:`~matplotlib.lines.Line2D` kwargs are + + %(Line2D)s + + """ + if len(kwargs): + b = True + b = _string_to_bool(b) + + if axis == 'x' or axis == 'both': + self.xaxis.grid(b, which=which, **kwargs) + if axis == 'y' or axis == 'both': + self.yaxis.grid(b, which=which, **kwargs) + + def ticklabel_format(self, **kwargs): + """ + Change the `~matplotlib.ticker.ScalarFormatter` used by + default for linear axes. + + Optional keyword arguments: + + ============ ========================================= + Keyword Description + ============ ========================================= + *style* [ 'sci' (or 'scientific') | 'plain' ] + plain turns off scientific notation + *scilimits* (m, n), pair of integers; if *style* + is 'sci', scientific notation will + be used for numbers outside the range + 10`m`:sup: to 10`n`:sup:. + Use (0,0) to include all numbers. + *useOffset* [True | False | offset]; if True, + the offset will be calculated as needed; + if False, no offset will be used; if a + numeric offset is specified, it will be + used. + *axis* [ 'x' | 'y' | 'both' ] + *useLocale* If True, format the number according to + the current locale. This affects things + such as the character used for the + decimal separator. If False, use + C-style (English) formatting. The + default setting is controlled by the + axes.formatter.use_locale rcparam. + ============ ========================================= + + Only the major ticks are affected. + If the method is called when the + :class:`~matplotlib.ticker.ScalarFormatter` is not the + :class:`~matplotlib.ticker.Formatter` being used, an + :exc:`AttributeError` will be raised. + + """ + style = kwargs.pop('style', '').lower() + scilimits = kwargs.pop('scilimits', None) + useOffset = kwargs.pop('useOffset', None) + useLocale = kwargs.pop('useLocale', None) + axis = kwargs.pop('axis', 'both').lower() + if scilimits is not None: + try: + m, n = scilimits + m + n + 1 # check that both are numbers + except (ValueError, TypeError): + raise ValueError("scilimits must be a sequence of 2 integers") + if style[:3] == 'sci': + sb = True + elif style in ['plain', 'comma']: + sb = False + if style == 'plain': + cb = False + else: + cb = True + raise NotImplementedError("comma style remains to be added") + elif style == '': + sb = None + else: + raise ValueError("%s is not a valid style value") + try: + if sb is not None: + if axis == 'both' or axis == 'x': + self.xaxis.major.formatter.set_scientific(sb) + if axis == 'both' or axis == 'y': + self.yaxis.major.formatter.set_scientific(sb) + if scilimits is not None: + if axis == 'both' or axis == 'x': + self.xaxis.major.formatter.set_powerlimits(scilimits) + if axis == 'both' or axis == 'y': + self.yaxis.major.formatter.set_powerlimits(scilimits) + if useOffset is not None: + if axis == 'both' or axis == 'x': + self.xaxis.major.formatter.set_useOffset(useOffset) + if axis == 'both' or axis == 'y': + self.yaxis.major.formatter.set_useOffset(useOffset) + if useLocale is not None: + if axis == 'both' or axis == 'x': + self.xaxis.major.formatter.set_useLocale(useLocale) + if axis == 'both' or axis == 'y': + self.yaxis.major.formatter.set_useLocale(useLocale) + except AttributeError: + raise AttributeError( + "This method only works with the ScalarFormatter.") + + def locator_params(self, axis='both', tight=None, **kwargs): + """ + Control behavior of tick locators. + + Keyword arguments: + + *axis* + ['x' | 'y' | 'both'] Axis on which to operate; + default is 'both'. + + *tight* + [True | False | None] Parameter passed to :meth:`autoscale_view`. + Default is None, for no change. + + Remaining keyword arguments are passed to directly to the + :meth:`~matplotlib.ticker.MaxNLocator.set_params` method. + + Typically one might want to reduce the maximum number + of ticks and use tight bounds when plotting small + subplots, for example:: + + ax.locator_params(tight=True, nbins=4) + + Because the locator is involved in autoscaling, + :meth:`autoscale_view` is called automatically after + the parameters are changed. + + This presently works only for the + :class:`~matplotlib.ticker.MaxNLocator` used + by default on linear axes, but it may be generalized. + """ + _x = axis in ['x', 'both'] + _y = axis in ['y', 'both'] + if _x: + self.xaxis.get_major_locator().set_params(**kwargs) + if _y: + self.yaxis.get_major_locator().set_params(**kwargs) + self.autoscale_view(tight=tight, scalex=_x, scaley=_y) + + def tick_params(self, axis='both', **kwargs): + """ + Change the appearance of ticks and tick labels. + + Keyword arguments: + + *axis* : ['x' | 'y' | 'both'] + Axis on which to operate; default is 'both'. + + *reset* : [True | False] + If *True*, set all parameters to defaults + before processing other keyword arguments. Default is + *False*. + + *which* : ['major' | 'minor' | 'both'] + Default is 'major'; apply arguments to *which* ticks. + + *direction* : ['in' | 'out' | 'inout'] + Puts ticks inside the axes, outside the axes, or both. + + *length* + Tick length in points. + + *width* + Tick width in points. + + *color* + Tick color; accepts any mpl color spec. + + *pad* + Distance in points between tick and label. + + *labelsize* + Tick label font size in points or as a string (e.g., 'large'). + + *labelcolor* + Tick label color; mpl color spec. + + *colors* + Changes the tick color and the label color to the same value: + mpl color spec. + + *zorder* + Tick and label zorder. + + *bottom*, *top*, *left*, *right* : [bool | 'on' | 'off'] + controls whether to draw the respective ticks. + + *labelbottom*, *labeltop*, *labelleft*, *labelright* + Boolean or ['on' | 'off'], controls whether to draw the + respective tick labels. + + Example:: + + ax.tick_params(direction='out', length=6, width=2, colors='r') + + This will make all major ticks be red, pointing out of the box, + and with dimensions 6 points by 2 points. Tick labels will + also be red. + + """ + if axis in ['x', 'both']: + xkw = dict(kwargs) + xkw.pop('left', None) + xkw.pop('right', None) + xkw.pop('labelleft', None) + xkw.pop('labelright', None) + self.xaxis.set_tick_params(**xkw) + if axis in ['y', 'both']: + ykw = dict(kwargs) + ykw.pop('top', None) + ykw.pop('bottom', None) + ykw.pop('labeltop', None) + ykw.pop('labelbottom', None) + self.yaxis.set_tick_params(**ykw) + + def set_axis_off(self): + """turn off the axis""" + self.axison = False + + def set_axis_on(self): + """turn on the axis""" + self.axison = True + + def get_axis_bgcolor(self): + """Return the axis background color""" + return self._axisbg + + def set_axis_bgcolor(self, color): + """ + set the axes background color + + ACCEPTS: any matplotlib color - see + :func:`~matplotlib.pyplot.colors` + """ + + self._axisbg = color + self.patch.set_facecolor(color) + + ### data limits, ticks, tick labels, and formatting + + def invert_xaxis(self): + "Invert the x-axis." + left, right = self.get_xlim() + self.set_xlim(right, left, auto=None) + + def xaxis_inverted(self): + """Returns *True* if the x-axis is inverted.""" + left, right = self.get_xlim() + return right < left + + def get_xbound(self): + """ + Returns the x-axis numerical bounds where:: + + lowerBound < upperBound + + """ + left, right = self.get_xlim() + if left < right: + return left, right + else: + return right, left + + def set_xbound(self, lower=None, upper=None): + """ + Set the lower and upper numerical bounds of the x-axis. + This method will honor axes inversion regardless of parameter order. + It will not change the _autoscaleXon attribute. + """ + if upper is None and iterable(lower): + lower, upper = lower + + old_lower, old_upper = self.get_xbound() + + if lower is None: + lower = old_lower + if upper is None: + upper = old_upper + + if self.xaxis_inverted(): + if lower < upper: + self.set_xlim(upper, lower, auto=None) + else: + self.set_xlim(lower, upper, auto=None) + else: + if lower < upper: + self.set_xlim(lower, upper, auto=None) + else: + self.set_xlim(upper, lower, auto=None) + + def get_xlim(self): + """ + Get the x-axis range [*left*, *right*] + """ + return tuple(self.viewLim.intervalx) + + def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): + """ + Call signature:: + + set_xlim(self, *args, **kwargs): + + Set the data limits for the xaxis + + Examples:: + + set_xlim((left, right)) + set_xlim(left, right) + set_xlim(left=1) # right unchanged + set_xlim(right=1) # left unchanged + + Keyword arguments: + + *left*: scalar + The left xlim; *xmin*, the previous name, may still be used + + *right*: scalar + The right xlim; *xmax*, the previous name, may still be used + + *emit*: [ *True* | *False* ] + Notify observers of limit change + + *auto*: [ *True* | *False* | *None* ] + Turn *x* autoscaling on (*True*), off (*False*; default), + or leave unchanged (*None*) + + Note, the *left* (formerly *xmin*) value may be greater than + the *right* (formerly *xmax*). + For example, suppose *x* is years before present. + Then one might use:: + + set_ylim(5000, 0) + + so 5000 years ago is on the left of the plot and the + present is on the right. + + Returns the current xlimits as a length 2 tuple + + ACCEPTS: length 2 sequence of floats + """ + if 'xmin' in kw: + left = kw.pop('xmin') + if 'xmax' in kw: + right = kw.pop('xmax') + if kw: + raise ValueError("unrecognized kwargs: %s" % kw.keys()) + + if right is None and iterable(left): + left, right = left + + self._process_unit_info(xdata=(left, right)) + if left is not None: + left = self.convert_xunits(left) + if right is not None: + right = self.convert_xunits(right) + + old_left, old_right = self.get_xlim() + if left is None: + left = old_left + if right is None: + right = old_right + + if left == right: + warnings.warn( + ('Attempting to set identical left==right results\n' + 'in singular transformations; automatically expanding.\n' + 'left=%s, right=%s') % (left, right)) + left, right = mtransforms.nonsingular(left, right, increasing=False) + left, right = self.xaxis.limit_range_for_scale(left, right) + + self.viewLim.intervalx = (left, right) + if auto is not None: + self._autoscaleXon = bool(auto) + + if emit: + self.callbacks.process('xlim_changed', self) + # Call all of the other x-axes that are shared with this one + for other in self._shared_x_axes.get_siblings(self): + if other is not self: + other.set_xlim(self.viewLim.intervalx, + emit=False, auto=auto) + if (other.figure != self.figure and + other.figure.canvas is not None): + other.figure.canvas.draw_idle() + + return left, right + + def get_xscale(self): + return self.xaxis.get_scale() + get_xscale.__doc__ = "Return the xaxis scale string: %s""" % ( + ", ".join(mscale.get_scale_names())) + + @docstring.dedent_interpd + def set_xscale(self, value, **kwargs): + """ + Call signature:: + + set_xscale(value) + + Set the scaling of the x-axis: %(scale)s + + ACCEPTS: [%(scale)s] + + Different kwargs are accepted, depending on the scale: + %(scale_docs)s + """ + self.xaxis._set_scale(value, **kwargs) + self.autoscale_view(scaley=False) + self._update_transScale() + + def get_xticks(self, minor=False): + """Return the x ticks as a list of locations""" + return self.xaxis.get_ticklocs(minor=minor) + + def set_xticks(self, ticks, minor=False): + """ + Set the x ticks with list of *ticks* + + ACCEPTS: sequence of floats + """ + return self.xaxis.set_ticks(ticks, minor=minor) + + def get_xmajorticklabels(self): + """ + Get the xtick labels as a list of :class:`~matplotlib.text.Text` + instances. + """ + return cbook.silent_list('Text xticklabel', + self.xaxis.get_majorticklabels()) + + def get_xminorticklabels(self): + """ + Get the x minor tick labels as a list of + :class:`matplotlib.text.Text` instances. + """ + return cbook.silent_list('Text xticklabel', + self.xaxis.get_minorticklabels()) + + def get_xticklabels(self, minor=False): + """ + Get the x tick labels as a list of :class:`~matplotlib.text.Text` + instances. + """ + return cbook.silent_list('Text xticklabel', + self.xaxis.get_ticklabels(minor=minor)) + + @docstring.dedent_interpd + def set_xticklabels(self, labels, fontdict=None, minor=False, **kwargs): + """ + Call signature:: + + set_xticklabels(labels, fontdict=None, minor=False, **kwargs) + + Set the xtick labels with list of strings *labels*. Return a + list of axis text instances. + + *kwargs* set the :class:`~matplotlib.text.Text` properties. + Valid properties are + %(Text)s + + ACCEPTS: sequence of strings + """ + return self.xaxis.set_ticklabels(labels, fontdict, + minor=minor, **kwargs) + + def invert_yaxis(self): + """ + Invert the y-axis. + """ + bottom, top = self.get_ylim() + self.set_ylim(top, bottom, auto=None) + + def yaxis_inverted(self): + """Returns *True* if the y-axis is inverted.""" + bottom, top = self.get_ylim() + return top < bottom + + def get_ybound(self): + """ + Return y-axis numerical bounds in the form of + ``lowerBound < upperBound`` + """ + bottom, top = self.get_ylim() + if bottom < top: + return bottom, top + else: + return top, bottom + + def set_ybound(self, lower=None, upper=None): + """ + Set the lower and upper numerical bounds of the y-axis. + This method will honor axes inversion regardless of parameter order. + It will not change the _autoscaleYon attribute. + """ + if upper is None and iterable(lower): + lower, upper = lower + + old_lower, old_upper = self.get_ybound() + + if lower is None: + lower = old_lower + if upper is None: + upper = old_upper + + if self.yaxis_inverted(): + if lower < upper: + self.set_ylim(upper, lower, auto=None) + else: + self.set_ylim(lower, upper, auto=None) + else: + if lower < upper: + self.set_ylim(lower, upper, auto=None) + else: + self.set_ylim(upper, lower, auto=None) + + def get_ylim(self): + """ + Get the y-axis range [*bottom*, *top*] + """ + return tuple(self.viewLim.intervaly) + + def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): + """ + Call signature:: + + set_ylim(self, *args, **kwargs): + + Set the data limits for the yaxis + + Examples:: + + set_ylim((bottom, top)) + set_ylim(bottom, top) + set_ylim(bottom=1) # top unchanged + set_ylim(top=1) # bottom unchanged + + Keyword arguments: + + *bottom*: scalar + The bottom ylim; the previous name, *ymin*, may still be used + + *top*: scalar + The top ylim; the previous name, *ymax*, may still be used + + *emit*: [ *True* | *False* ] + Notify observers of limit change + + *auto*: [ *True* | *False* | *None* ] + Turn *y* autoscaling on (*True*), off (*False*; default), + or leave unchanged (*None*) + + Note, the *bottom* (formerly *ymin*) value may be greater than + the *top* (formerly *ymax*). + For example, suppose *y* is depth in the ocean. + Then one might use:: + + set_ylim(5000, 0) + + so 5000 m depth is at the bottom of the plot and the + surface, 0 m, is at the top. + + Returns the current ylimits as a length 2 tuple + + ACCEPTS: length 2 sequence of floats + """ + if 'ymin' in kw: + bottom = kw.pop('ymin') + if 'ymax' in kw: + top = kw.pop('ymax') + if kw: + raise ValueError("unrecognized kwargs: %s" % kw.keys()) + + if top is None and iterable(bottom): + bottom, top = bottom + + if bottom is not None: + bottom = self.convert_yunits(bottom) + if top is not None: + top = self.convert_yunits(top) + + old_bottom, old_top = self.get_ylim() + + if bottom is None: + bottom = old_bottom + if top is None: + top = old_top + + if bottom == top: + warnings.warn(('Attempting to set identical bottom==top results\n' + + 'in singular transformations; automatically expanding.\n' + + 'bottom=%s, top=%s') % (bottom, top)) + + bottom, top = mtransforms.nonsingular(bottom, top, increasing=False) + bottom, top = self.yaxis.limit_range_for_scale(bottom, top) + + self.viewLim.intervaly = (bottom, top) + if auto is not None: + self._autoscaleYon = bool(auto) + + if emit: + self.callbacks.process('ylim_changed', self) + # Call all of the other y-axes that are shared with this one + for other in self._shared_y_axes.get_siblings(self): + if other is not self: + other.set_ylim(self.viewLim.intervaly, + emit=False, auto=auto) + if (other.figure != self.figure and + other.figure.canvas is not None): + other.figure.canvas.draw_idle() + + return bottom, top + + def get_yscale(self): + return self.yaxis.get_scale() + get_yscale.__doc__ = "Return the yaxis scale string: %s""" % ( + ", ".join(mscale.get_scale_names())) + + @docstring.dedent_interpd + def set_yscale(self, value, **kwargs): + """ + Call signature:: + + set_yscale(value) + + Set the scaling of the y-axis: %(scale)s + + ACCEPTS: [%(scale)s] + + Different kwargs are accepted, depending on the scale: + %(scale_docs)s + """ + self.yaxis._set_scale(value, **kwargs) + self.autoscale_view(scalex=False) + self._update_transScale() + + def get_yticks(self, minor=False): + """Return the y ticks as a list of locations""" + return self.yaxis.get_ticklocs(minor=minor) + + def set_yticks(self, ticks, minor=False): + """ + Set the y ticks with list of *ticks* + + ACCEPTS: sequence of floats + + Keyword arguments: + + *minor*: [ *False* | *True* ] + Sets the minor ticks if *True* + """ + return self.yaxis.set_ticks(ticks, minor=minor) + + def get_ymajorticklabels(self): + """ + Get the major y tick labels as a list of + :class:`~matplotlib.text.Text` instances. + """ + return cbook.silent_list('Text yticklabel', + self.yaxis.get_majorticklabels()) + + def get_yminorticklabels(self): + """ + Get the minor y tick labels as a list of + :class:`~matplotlib.text.Text` instances. + """ + return cbook.silent_list('Text yticklabel', + self.yaxis.get_minorticklabels()) + + def get_yticklabels(self, minor=False): + """ + Get the y tick labels as a list of :class:`~matplotlib.text.Text` + instances + """ + return cbook.silent_list('Text yticklabel', + self.yaxis.get_ticklabels(minor=minor)) + + @docstring.dedent_interpd + def set_yticklabels(self, labels, fontdict=None, minor=False, **kwargs): + """ + Call signature:: + + set_yticklabels(labels, fontdict=None, minor=False, **kwargs) + + Set the y tick labels with list of strings *labels*. Return a list of + :class:`~matplotlib.text.Text` instances. + + *kwargs* set :class:`~matplotlib.text.Text` properties for the labels. + Valid properties are + %(Text)s + + ACCEPTS: sequence of strings + """ + return self.yaxis.set_ticklabels(labels, fontdict, + minor=minor, **kwargs) + + def xaxis_date(self, tz=None): + """ + Sets up x-axis ticks and labels that treat the x data as dates. + + *tz* is a timezone string or :class:`tzinfo` instance. + Defaults to rc value. + """ + # should be enough to inform the unit conversion interface + # dates are coming in + self.xaxis.axis_date(tz) + + def yaxis_date(self, tz=None): + """ + Sets up y-axis ticks and labels that treat the y data as dates. + + *tz* is a timezone string or :class:`tzinfo` instance. + Defaults to rc value. + """ + self.yaxis.axis_date(tz) + + def format_xdata(self, x): + """ + Return *x* string formatted. This function will use the attribute + self.fmt_xdata if it is callable, else will fall back on the xaxis + major formatter + """ + try: + return self.fmt_xdata(x) + except TypeError: + func = self.xaxis.get_major_formatter().format_data_short + val = func(x) + return val + + def format_ydata(self, y): + """ + Return y string formatted. This function will use the + :attr:`fmt_ydata` attribute if it is callable, else will fall + back on the yaxis major formatter + """ + try: + return self.fmt_ydata(y) + except TypeError: + func = self.yaxis.get_major_formatter().format_data_short + val = func(y) + return val + + def format_coord(self, x, y): + """Return a format string formatting the *x*, *y* coord""" + if x is None: + xs = '???' + else: + xs = self.format_xdata(x) + if y is None: + ys = '???' + else: + ys = self.format_ydata(y) + return 'x=%s y=%s' % (xs, ys) + + #### Interactive manipulation + + def can_zoom(self): + """ + Return *True* if this axes supports the zoom box button functionality. + """ + return True + + def can_pan(self): + """ + Return *True* if this axes supports any pan/zoom button functionality. + """ + return True + + def get_navigate(self): + """ + Get whether the axes responds to navigation commands + """ + return self._navigate + + def set_navigate(self, b): + """ + Set whether the axes responds to navigation toolbar commands + + ACCEPTS: [ *True* | *False* ] + """ + self._navigate = b + + def get_navigate_mode(self): + """ + Get the navigation toolbar button status: 'PAN', 'ZOOM', or None + """ + return self._navigate_mode + + def set_navigate_mode(self, b): + """ + Set the navigation toolbar button status; + + .. warning:: + this is not a user-API function. + + """ + self._navigate_mode = b + + def start_pan(self, x, y, button): + """ + Called when a pan operation has started. + + *x*, *y* are the mouse coordinates in display coords. + button is the mouse button number: + + * 1: LEFT + * 2: MIDDLE + * 3: RIGHT + + .. note:: + + Intended to be overridden by new projection types. + + """ + self._pan_start = cbook.Bunch( + lim=self.viewLim.frozen(), + trans=self.transData.frozen(), + trans_inverse=self.transData.inverted().frozen(), + bbox=self.bbox.frozen(), + x=x, + y=y) + + def end_pan(self): + """ + Called when a pan operation completes (when the mouse button + is up.) + + .. note:: + + Intended to be overridden by new projection types. + + """ + del self._pan_start + + def drag_pan(self, button, key, x, y): + """ + Called when the mouse moves during a pan operation. + + *button* is the mouse button number: + + * 1: LEFT + * 2: MIDDLE + * 3: RIGHT + + *key* is a "shift" key + + *x*, *y* are the mouse coordinates in display coords. + + .. note:: + + Intended to be overridden by new projection types. + + """ + def format_deltas(key, dx, dy): + if key == 'control': + if abs(dx) > abs(dy): + dy = dx + else: + dx = dy + elif key == 'x': + dy = 0 + elif key == 'y': + dx = 0 + elif key == 'shift': + if 2 * abs(dx) < abs(dy): + dx = 0 + elif 2 * abs(dy) < abs(dx): + dy = 0 + elif abs(dx) > abs(dy): + dy = dy / abs(dy) * abs(dx) + else: + dx = dx / abs(dx) * abs(dy) + return (dx, dy) + + p = self._pan_start + dx = x - p.x + dy = y - p.y + if dx == 0 and dy == 0: + return + if button == 1: + dx, dy = format_deltas(key, dx, dy) + result = p.bbox.translated(-dx, -dy) \ + .transformed(p.trans_inverse) + elif button == 3: + try: + dx = -dx / float(self.bbox.width) + dy = -dy / float(self.bbox.height) + dx, dy = format_deltas(key, dx, dy) + if self.get_aspect() != 'auto': + dx = 0.5 * (dx + dy) + dy = dx + + alpha = np.power(10.0, (dx, dy)) + start = np.array([p.x, p.y]) + oldpoints = p.lim.transformed(p.trans) + newpoints = start + alpha * (oldpoints - start) + result = mtransforms.Bbox(newpoints) \ + .transformed(p.trans_inverse) + except OverflowError: + warnings.warn('Overflow while panning') + return + + self.set_xlim(*result.intervalx) + self.set_ylim(*result.intervaly) + + def get_cursor_props(self): + """ + Return the cursor propertiess as a (*linewidth*, *color*) + tuple, where *linewidth* is a float and *color* is an RGBA + tuple + """ + return self._cursorProps + + def set_cursor_props(self, *args): + """ + Set the cursor property as:: + + ax.set_cursor_props(linewidth, color) + + or:: + + ax.set_cursor_props((linewidth, color)) + + ACCEPTS: a (*float*, *color*) tuple + """ + if len(args) == 1: + lw, c = args[0] + elif len(args) == 2: + lw, c = args + else: + raise ValueError('args must be a (linewidth, color) tuple') + c = mcolors.colorConverter.to_rgba(c) + self._cursorProps = lw, c + + def get_children(self): + """return a list of child artists""" + children = [] + children.append(self.xaxis) + children.append(self.yaxis) + children.extend(self.lines) + children.extend(self.patches) + children.extend(self.texts) + children.extend(self.tables) + children.extend(self.artists) + children.extend(self.images) + if self.legend_ is not None: + children.append(self.legend_) + children.extend(self.collections) + children.append(self.title) + children.append(self._left_title) + children.append(self._right_title) + children.append(self.patch) + children.extend(self.spines.itervalues()) + return children + + def contains(self, mouseevent): + """ + Test whether the mouse event occured in the axes. + + Returns *True* / *False*, {} + """ + if callable(self._contains): + return self._contains(self, mouseevent) + + return self.patch.contains(mouseevent) + + def contains_point(self, point): + """ + Returns *True* if the point (tuple of x,y) is inside the axes + (the area defined by the its patch). A pixel coordinate is + required. + + """ + return self.patch.contains_point(point, radius=1.0) + + def pick(self, *args): + """ + Call signature:: + + pick(mouseevent) + + each child artist will fire a pick event if mouseevent is over + the artist and the artist has picker set + """ + martist.Artist.pick(self, args[0]) diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py new file mode 100644 index 000000000000..af2e15cd6e7c --- /dev/null +++ b/lib/matplotlib/axes/_subplots.py @@ -0,0 +1,209 @@ +from matplotlib.gridspec import GridSpec, SubplotSpec +from matplotlib import docstring +import matplotlib.artist as martist +from matplotlib.axes._axes import Axes + + +class SubplotBase(object): + """ + Base class for subplots, which are :class:`Axes` instances with + additional methods to facilitate generating and manipulating a set + of :class:`Axes` within a figure. + """ + + def __init__(self, fig, *args, **kwargs): + """ + *fig* is a :class:`matplotlib.figure.Figure` instance. + + *args* is the tuple (*numRows*, *numCols*, *plotNum*), where + the array of subplots in the figure has dimensions *numRows*, + *numCols*, and where *plotNum* is the number of the subplot + being created. *plotNum* starts at 1 in the upper left + corner and increases to the right. + + + If *numRows* <= *numCols* <= *plotNum* < 10, *args* can be the + decimal integer *numRows* * 100 + *numCols* * 10 + *plotNum*. + """ + + self.figure = fig + + if len(args) == 1: + if isinstance(args[0], SubplotSpec): + self._subplotspec = args[0] + else: + try: + s = str(int(args[0])) + rows, cols, num = map(int, s) + except ValueError: + raise ValueError( + 'Single argument to subplot must be a 3-digit ' + 'integer') + self._subplotspec = GridSpec(rows, cols)[num - 1] + # num - 1 for converting from MATLAB to python indexing + elif len(args) == 3: + rows, cols, num = args + rows = int(rows) + cols = int(cols) + if isinstance(num, tuple) and len(num) == 2: + num = [int(n) for n in num] + self._subplotspec = GridSpec(rows, cols)[num[0] - 1:num[1]] + else: + self._subplotspec = GridSpec(rows, cols)[int(num) - 1] + # num - 1 for converting from MATLAB to python indexing + else: + raise ValueError('Illegal argument(s) to subplot: %s' % (args,)) + + self.update_params() + + # _axes_class is set in the subplot_class_factory + self._axes_class.__init__(self, fig, self.figbox, **kwargs) + + def __reduce__(self): + # get the first axes class which does not inherit from a subplotbase + not_subplotbase = lambda c: issubclass(c, Axes) and \ + not issubclass(c, SubplotBase) + axes_class = [c for c in self.__class__.mro() if not_subplotbase(c)][0] + r = [_PicklableSubplotClassConstructor(), + (axes_class,), + self.__getstate__()] + return tuple(r) + + def get_geometry(self): + """get the subplot geometry, eg 2,2,3""" + rows, cols, num1, num2 = self.get_subplotspec().get_geometry() + return rows, cols, num1 + 1 # for compatibility + + # COVERAGE NOTE: Never used internally or from examples + def change_geometry(self, numrows, numcols, num): + """change subplot geometry, e.g., from 1,1,1 to 2,2,3""" + self._subplotspec = GridSpec(numrows, numcols)[num - 1] + self.update_params() + self.set_position(self.figbox) + + def get_subplotspec(self): + """get the SubplotSpec instance associated with the subplot""" + return self._subplotspec + + def set_subplotspec(self, subplotspec): + """set the SubplotSpec instance associated with the subplot""" + self._subplotspec = subplotspec + + def update_params(self): + """update the subplot position from fig.subplotpars""" + + self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = \ + self.get_subplotspec().get_position(self.figure, + return_all=True) + + def is_first_col(self): + return self.colNum == 0 + + def is_first_row(self): + return self.rowNum == 0 + + def is_last_row(self): + return self.rowNum == self.numRows - 1 + + def is_last_col(self): + return self.colNum == self.numCols - 1 + + # COVERAGE NOTE: Never used internally or from examples + def label_outer(self): + """ + set the visible property on ticklabels so xticklabels are + visible only if the subplot is in the last row and yticklabels + are visible only if the subplot is in the first column + """ + lastrow = self.is_last_row() + firstcol = self.is_first_col() + for label in self.get_xticklabels(): + label.set_visible(lastrow) + + for label in self.get_yticklabels(): + label.set_visible(firstcol) + + def _make_twin_axes(self, *kl, **kwargs): + """ + make a twinx axes of self. This is used for twinx and twiny. + """ + from matplotlib.projections import process_projection_requirements + kl = (self.get_subplotspec(),) + kl + projection_class, kwargs, key = process_projection_requirements( + self.figure, *kl, **kwargs) + + ax2 = subplot_class_factory(projection_class)(self.figure, + *kl, **kwargs) + self.figure.add_subplot(ax2) + return ax2 + +_subplot_classes = {} + + +def subplot_class_factory(axes_class=None): + # This makes a new class that inherits from SubplotBase and the + # given axes_class (which is assumed to be a subclass of Axes). + # This is perhaps a little bit roundabout to make a new class on + # the fly like this, but it means that a new Subplot class does + # not have to be created for every type of Axes. + if axes_class is None: + axes_class = Axes + + new_class = _subplot_classes.get(axes_class) + if new_class is None: + new_class = type("%sSubplot" % (axes_class.__name__), + (SubplotBase, axes_class), + {'_axes_class': axes_class}) + _subplot_classes[axes_class] = new_class + + return new_class + +# This is provided for backward compatibility +Subplot = subplot_class_factory() + + +class _PicklableSubplotClassConstructor(object): + """ + This stub class exists to return the appropriate subplot + class when __call__-ed with an axes class. This is purely to + allow Pickling of Axes and Subplots. + """ + def __call__(self, axes_class): + # create a dummy object instance + subplot_instance = _PicklableSubplotClassConstructor() + subplot_class = subplot_class_factory(axes_class) + # update the class to the desired subplot class + subplot_instance.__class__ = subplot_class + return subplot_instance + + +docstring.interpd.update(Axes=martist.kwdoc(Axes)) +docstring.interpd.update(Subplot=martist.kwdoc(Axes)) + +""" +# this is some discarded code I was using to find the minimum positive +# data point for some log scaling fixes. I realized there was a +# cleaner way to do it, but am keeping this around as an example for +# how to get the data out of the axes. Might want to make something +# like this a method one day, or better yet make get_verts an Artist +# method + + minx, maxx = self.get_xlim() + if minx<=0 or maxx<=0: + # find the min pos value in the data + xs = [] + for line in self.lines: + xs.extend(line.get_xdata(orig=False)) + for patch in self.patches: + xs.extend([x for x,y in patch.get_verts()]) + for collection in self.collections: + xs.extend([x for x,y in collection.get_verts()]) + posx = [x for x in xs if x>0] + if len(posx): + + minx = min(posx) + maxx = max(posx) + # warning, probably breaks inverted axis + self.set_xlim((0.1*minx, maxx)) + +""" diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index bf0d509725e4..1eae891360a3 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -893,7 +893,7 @@ def _as_mpl_axes(self): # testing axes creation with gca ax = plt.gca(projection=prj) - assert type(ax) == maxes._subplot_classes[PolarAxes], \ + assert type(ax) == maxes._subplots._subplot_classes[PolarAxes], \ 'Expected a PolarAxesSubplot, got %s' % type(ax) ax_via_gca = plt.gca(projection=prj) assert ax_via_gca is ax @@ -909,7 +909,7 @@ def _as_mpl_axes(self): # testing axes creation with subplot ax = plt.subplot(121, projection=prj) - assert type(ax) == maxes._subplot_classes[PolarAxes], \ + assert type(ax) == maxes._subplots._subplot_classes[PolarAxes], \ 'Expected a PolarAxesSubplot, got %s' % type(ax) plt.close() diff --git a/setupext.py b/setupext.py index 5cec57d1dc55..52c1e716f0b8 100644 --- a/setupext.py +++ b/setupext.py @@ -511,6 +511,7 @@ def get_packages(self): 'matplotlib.backends.qt4_editor', 'matplotlib.compat', 'matplotlib.projections', + 'matplotlib.axes', 'matplotlib.sphinxext', 'matplotlib.testing', 'matplotlib.testing.jpl_units', From fcdcd555968e9def758eae64d72f831fa339b600 Mon Sep 17 00:00:00 2001 From: Nelle Varoquaux Date: Fri, 28 Jun 2013 22:16:57 +0200 Subject: [PATCH 2/8] FIX process_plot_format is not in the axes module anymore --- lib/matplotlib/tri/triplot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tri/triplot.py b/lib/matplotlib/tri/triplot.py index 7d049c4869f0..792713907a01 100644 --- a/lib/matplotlib/tri/triplot.py +++ b/lib/matplotlib/tri/triplot.py @@ -5,6 +5,7 @@ from matplotlib.tri.triangulation import Triangulation import numpy as np + def triplot(ax, *args, **kwargs): """ Draw a unstructured triangular grid as lines and/or markers. @@ -53,7 +54,7 @@ def triplot(ax, *args, **kwargs): fmt = '' if len(args) > 0: fmt = args[0] - linestyle, marker, color = matplotlib.axes._process_plot_format(fmt) + linestyle, marker, color = matplotlib.axes._base._process_plot_format(fmt) # Draw lines without markers, if lines are required. if linestyle is not None and linestyle is not 'None': From 96ee5b2fdfd1cabb387791f4079689ae5586f4e7 Mon Sep 17 00:00:00 2001 From: Nelle Varoquaux Date: Fri, 28 Jun 2013 22:17:37 +0200 Subject: [PATCH 3/8] ENH now can run the tests on triangulation without the test runner --- lib/matplotlib/tests/test_triangulation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/matplotlib/tests/test_triangulation.py b/lib/matplotlib/tests/test_triangulation.py index 2d04d154e48d..1847c912dbc9 100644 --- a/lib/matplotlib/tests/test_triangulation.py +++ b/lib/matplotlib/tests/test_triangulation.py @@ -881,3 +881,8 @@ def meshgrid_triangles(n): d = (i+1) + (j+1)*n tri += [[a, b, d], [a, d, c]] return np.array(tri, dtype=np.int32) + + +if __name__ == '__main__': + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) From 3a6270c72c81c8f9ce997a24d3f7de6621f9955d Mon Sep 17 00:00:00 2001 From: Nelle Varoquaux Date: Sat, 29 Jun 2013 00:12:04 +0200 Subject: [PATCH 4/8] Documented the changes --- CHANGELOG | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 8d2c91c224cb..345d5f8f295f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +2013-06-26 Refactored the axes module: the axes module is now a folder, + containing the following submodule: + - _subplots.py, containing all the subplots helper methods + - _base.py, containing several private methods and a new + _AxesBase class. This _AxesBase class contains all the methods + that are not directly linked to plots of the "old" Axes + - _axes.py contains the Axes class. This class now inherits from + _AxesBase: it contains all "plotting" methods and labelling + methods. + This refactoring should not affect the API. Only private methods + are not importable from the axes module anymore. + 2013-05-18 Added support for arbitrary rasterization resolutions to the SVG backend. Previously the resolution was hard coded to 72 dpi. Now the backend class takes a image_dpi argument for From 7238e2c3e87409dc301e6953b737bb19ce9795d8 Mon Sep 17 00:00:00 2001 From: Nelle Varoquaux Date: Sat, 29 Jun 2013 00:27:54 +0200 Subject: [PATCH 5/8] DOC the refactoring of the axes module is now mentionned in the API_changes.rst file --- doc/api/api_changes.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index 6a54b2b0bdc4..11bfaffbd607 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -11,6 +11,18 @@ help figure out possible sources of the changes you are experiencing. For new features that were added to matplotlib, please see :ref:`whats-new`. +Changes in 1.4.x +================ + +* A major refactoring of the axes module was made. The axes module has been +splitted into smaller modules: + + - the `_base` module, which contains a new private _AxesBase class. This + class contains all methods except plotting and labelling methods. + - the `axes` module, which contains the Axes class. This class inherits + from _AxesBase, and contains all plotting and labelling methods. + - the `_subplot` module, with all the classes concerning subplotting. + .. _changes_in_1_3: From 14ec9785ce5d9a748dfbebdca36682edcde2365e Mon Sep 17 00:00:00 2001 From: Nelle Varoquaux Date: Sat, 29 Jun 2013 16:09:35 +0200 Subject: [PATCH 6/8] ENH shuffled around some extra methods between Axes and _AxesBase --- lib/matplotlib/axes/_axes.py | 814 +++++++++++++++-------------------- lib/matplotlib/axes/_base.py | 140 +++++- 2 files changed, 480 insertions(+), 474 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2937a68697d8..96c885b63362 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -9,7 +9,6 @@ import matplotlib rcParams = matplotlib.rcParams -import matplotlib.artist as martist import matplotlib.cbook as cbook import matplotlib.collections as mcoll import matplotlib.colors as mcolors @@ -24,7 +23,6 @@ import matplotlib.path as mpath import matplotlib.patches as mpatches import matplotlib.quiver as mquiver -import matplotlib.scale as mscale import matplotlib.stackplot as mstack import matplotlib.streamplot as mstream import matplotlib.table as mtable @@ -33,13 +31,16 @@ import matplotlib.transforms as mtransforms import matplotlib.tri as mtri from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer -from matplotlib.axes._base import _AxesBase, _string_to_bool +from matplotlib.axes._base import _AxesBase iterable = cbook.iterable is_string_like = cbook.is_string_like is_sequence_of_strings = cbook.is_sequence_of_strings +# The axes module contains all the wrappers to plotting functions. +# All the other methods should go in the _AxesBase class. + class Axes(_AxesBase): """ The :class:`Axes` contains most of the figure elements: @@ -54,7 +55,7 @@ class Axes(_AxesBase): 'ylim_changed' and the callback will be called with func(*ax*) where *ax* is the :class:`Axes` instance. """ - ### Labelling + ### Labelling, legend and texts def get_title(self, loc="center"): """Get an axes title. @@ -201,7 +202,253 @@ def set_ylabel(self, ylabel, fontdict=None, labelpad=None, **kwargs): self.yaxis.labelpad = labelpad return self.yaxis.set_label_text(ylabel, fontdict, **kwargs) - @docstring.dedent_interpd + def _get_legend_handles(self, legend_handler_map=None): + "return artists that will be used as handles for legend" + handles_original = (self.lines + self.patches + + self.collections + self.containers) + + # collections + handler_map = mlegend.Legend.get_default_handler_map() + + if legend_handler_map is not None: + handler_map = handler_map.copy() + handler_map.update(legend_handler_map) + + handles = [] + for h in handles_original: + if h.get_label() == "_nolegend_": # .startswith('_'): + continue + if mlegend.Legend.get_legend_handler(handler_map, h): + handles.append(h) + + return handles + + def get_legend_handles_labels(self, legend_handler_map=None): + """ + Return handles and labels for legend + + ``ax.legend()`` is equivalent to :: + + h, l = ax.get_legend_handles_labels() + ax.legend(h, l) + + """ + + handles = [] + labels = [] + for handle in self._get_legend_handles(legend_handler_map): + label = handle.get_label() + if label and not label.startswith('_'): + handles.append(handle) + labels.append(label) + + return handles, labels + + def legend(self, *args, **kwargs): + """ + Place a legend on the current axes. + + Call signature:: + + legend(*args, **kwargs) + + Places legend at location *loc*. Labels are a sequence of + strings and *loc* can be a string or an integer specifying the + legend location. + + To make a legend with existing lines:: + + legend() + + :meth:`legend` by itself will try and build a legend using the label + property of the lines/patches/collections. You can set the label of + a line by doing:: + + plot(x, y, label='my data') + + or:: + + line.set_label('my data'). + + If label is set to '_nolegend_', the item will not be shown in + legend. + + To automatically generate the legend from labels:: + + legend( ('label1', 'label2', 'label3') ) + + To make a legend for a list of lines and labels:: + + legend( (line1, line2, line3), ('label1', 'label2', 'label3') ) + + To make a legend at a given location, using a location argument:: + + legend( ('label1', 'label2', 'label3'), loc='upper left') + + or:: + + legend((line1, line2, line3), ('label1', 'label2', 'label3'), loc=2) + + The location codes are + + =============== ============= + Location String Location Code + =============== ============= + 'best' 0 + 'upper right' 1 + 'upper left' 2 + 'lower left' 3 + 'lower right' 4 + 'right' 5 + 'center left' 6 + 'center right' 7 + 'lower center' 8 + 'upper center' 9 + 'center' 10 + =============== ============= + + + Users can specify any arbitrary location for the legend using the + *bbox_to_anchor* keyword argument. bbox_to_anchor can be an instance + of BboxBase(or its derivatives) or a tuple of 2 or 4 floats. + For example:: + + loc = 'upper right', bbox_to_anchor = (0.5, 0.5) + + will place the legend so that the upper right corner of the legend at + the center of the axes. + + The legend location can be specified in other coordinate, by using the + *bbox_transform* keyword. + + The loc itslef can be a 2-tuple giving x,y of the lower-left corner of + the legend in axes coords (*bbox_to_anchor* is ignored). + + Keyword arguments: + + *prop*: [ *None* | FontProperties | dict ] + A :class:`matplotlib.font_manager.FontProperties` + instance. If *prop* is a dictionary, a new instance will be + created with *prop*. If *None*, use rc settings. + + *fontsize*: [size in points | 'xx-small' | 'x-small' | 'small' | + 'medium' | 'large' | 'x-large' | 'xx-large'] + Set the font size. May be either a size string, relative to + the default font size, or an absolute font size in points. This + argument is only used if prop is not specified. + + *numpoints*: integer + The number of points in the legend for line + + *scatterpoints*: integer + The number of points in the legend for scatter plot + + *scatteryoffsets*: list of floats + a list of yoffsets for scatter symbols in legend + + *markerscale*: [ *None* | scalar ] + The relative size of legend markers vs. original. If *None*, + use rc settings. + + *frameon*: [ *True* | *False* ] + if *True*, draw a frame around the legend. + The default is set by the rcParam 'legend.frameon' + + *fancybox*: [ *None* | *False* | *True* ] + if *True*, draw a frame with a round fancybox. If *None*, + use rc settings + + *shadow*: [ *None* | *False* | *True* ] + If *True*, draw a shadow behind legend. If *None*, + use rc settings. + + *framealpha*: [*None* | float] + If not None, alpha channel for legend frame. Default *None*. + + *ncol* : integer + number of columns. default is 1 + + *mode* : [ "expand" | *None* ] + if mode is "expand", the legend will be horizontally expanded + to fill the axes area (or *bbox_to_anchor*) + + *bbox_to_anchor*: an instance of BboxBase or a tuple of 2 or 4 floats + the bbox that the legend will be anchored. + + *bbox_transform* : [ an instance of Transform | *None* ] + the transform for the bbox. transAxes if *None*. + + *title* : string + the legend title + + Padding and spacing between various elements use following + keywords parameters. These values are measure in font-size + units. e.g., a fontsize of 10 points and a handlelength=5 + implies a handlelength of 50 points. Values from rcParams + will be used if None. + + ================ ==================================================== + Keyword Description + ================ ==================================================== + borderpad the fractional whitespace inside the legend border + labelspacing the vertical space between the legend entries + handlelength the length of the legend handles + handletextpad the pad between the legend handle and text + borderaxespad the pad between the axes and legend border + columnspacing the spacing between columns + ================ ==================================================== + + .. note:: + + Not all kinds of artist are supported by the legend command. + See :ref:`plotting-guide-legend` for details. + + **Example:** + + .. plot:: mpl_examples/api/legend_demo.py + + .. seealso:: + :ref:`plotting-guide-legend`. + + """ + + if len(args) == 0: + handles, labels = self.get_legend_handles_labels() + if len(handles) == 0: + warnings.warn("No labeled objects found. " + "Use label='...' kwarg on individual plots.") + return None + + elif len(args) == 1: + # LABELS + labels = args[0] + handles = [h for h, label in zip(self._get_legend_handles(), + labels)] + + elif len(args) == 2: + if is_string_like(args[1]) or isinstance(args[1], int): + # LABELS, LOC + labels, loc = args + handles = [h for h, label in zip(self._get_legend_handles(), + labels)] + kwargs['loc'] = loc + else: + # LINES, LABELS + handles, labels = args + + elif len(args) == 3: + # LINES, LABELS, LOC + handles, labels, loc = args + kwargs['loc'] = loc + else: + raise TypeError('Invalid arguments to legend') + + # Why do we need to call "flatten" here? -JJL + # handles = cbook.flatten(handles) + + self.legend_ = mlegend.Legend(self, handles, labels, **kwargs) + return self.legend_ + def text(self, x, y, s, fontdict=None, withdash=False, **kwargs): """ @@ -1300,384 +1547,137 @@ def acorr(self, x, **kwargs): - *lags* are a length 2*maxlags+1 lag vector - - *c* is the 2*maxlags+1 auto correlation vector - - - *line* is a :class:`~matplotlib.lines.Line2D` instance - returned by :meth:`plot` - - The default *linestyle* is None and the default *marker* is - ``'o'``, though these can be overridden with keyword args. - The cross correlation is performed with - :func:`numpy.correlate` with *mode* = 2. - - If *usevlines* is *True*, :meth:`~matplotlib.axes.Axes.vlines` - rather than :meth:`~matplotlib.axes.Axes.plot` is used to draw - vertical lines from the origin to the acorr. Otherwise, the - plot style is determined by the kwargs, which are - :class:`~matplotlib.lines.Line2D` properties. - - *maxlags* is a positive integer detailing the number of lags - to show. The default value of *None* will return all - ``(2*len(x)-1)`` lags. - - The return value is a tuple (*lags*, *c*, *linecol*, *b*) - where - - - *linecol* is the - :class:`~matplotlib.collections.LineCollection` - - - *b* is the *x*-axis. - - .. seealso:: - - :meth:`~matplotlib.axes.Axes.plot` or - :meth:`~matplotlib.axes.Axes.vlines` - For documentation on valid kwargs. - - **Example:** - - :func:`~matplotlib.pyplot.xcorr` is top graph, and - :func:`~matplotlib.pyplot.acorr` is bottom graph. - - .. plot:: mpl_examples/pylab_examples/xcorr_demo.py - """ - return self.xcorr(x, x, **kwargs) - - @docstring.dedent_interpd - def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, - usevlines=True, maxlags=10, **kwargs): - """ - Plot the cross correlation between *x* and *y*. - - Call signature:: - - xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, - usevlines=True, maxlags=10, **kwargs) - - If *normed* = *True*, normalize the data by the cross - correlation at 0-th lag. *x* and y are detrended by the - *detrend* callable (default no normalization). *x* and *y* - must be equal length. - - Data are plotted as ``plot(lags, c, **kwargs)`` - - Return value is a tuple (*lags*, *c*, *line*) where: - - - *lags* are a length ``2*maxlags+1`` lag vector - - - *c* is the ``2*maxlags+1`` auto correlation vector - - - *line* is a :class:`~matplotlib.lines.Line2D` instance - returned by :func:`~matplotlib.pyplot.plot`. - - The default *linestyle* is *None* and the default *marker* is - 'o', though these can be overridden with keyword args. The - cross correlation is performed with :func:`numpy.correlate` - with *mode* = 2. - - If *usevlines* is *True*: - - :func:`~matplotlib.pyplot.vlines` - rather than :func:`~matplotlib.pyplot.plot` is used to draw - vertical lines from the origin to the xcorr. Otherwise the - plotstyle is determined by the kwargs, which are - :class:`~matplotlib.lines.Line2D` properties. - - The return value is a tuple (*lags*, *c*, *linecol*, *b*) - where *linecol* is the - :class:`matplotlib.collections.LineCollection` instance and - *b* is the *x*-axis. - - *maxlags* is a positive integer detailing the number of lags to show. - The default value of *None* will return all ``(2*len(x)-1)`` lags. - - **Example:** - - :func:`~matplotlib.pyplot.xcorr` is top graph, and - :func:`~matplotlib.pyplot.acorr` is bottom graph. - - .. plot:: mpl_examples/pylab_examples/xcorr_demo.py - """ - - Nx = len(x) - if Nx != len(y): - raise ValueError('x and y must be equal length') - - x = detrend(np.asarray(x)) - y = detrend(np.asarray(y)) - - c = np.correlate(x, y, mode=2) - - if normed: - c /= np.sqrt(np.dot(x, x) * np.dot(y, y)) - - if maxlags is None: - maxlags = Nx - 1 - - if maxlags >= Nx or maxlags < 1: - raise ValueError('maglags must be None or strictly ' - 'positive < %d' % Nx) - - lags = np.arange(-maxlags, maxlags + 1) - c = c[Nx - 1 - maxlags:Nx + maxlags] - - if usevlines: - a = self.vlines(lags, [0], c, **kwargs) - b = self.axhline(**kwargs) - else: - - kwargs.setdefault('marker', 'o') - kwargs.setdefault('linestyle', 'None') - a, = self.plot(lags, c, **kwargs) - b = None - return lags, c, a, b - - def _get_legend_handles(self, legend_handler_map=None): - "return artists that will be used as handles for legend" - handles_original = self.lines + self.patches + \ - self.collections + self.containers - - # collections - handler_map = mlegend.Legend.get_default_handler_map() - - if legend_handler_map is not None: - handler_map = handler_map.copy() - handler_map.update(legend_handler_map) - - handles = [] - for h in handles_original: - if h.get_label() == "_nolegend_": # .startswith('_'): - continue - if mlegend.Legend.get_legend_handler(handler_map, h): - handles.append(h) - - return handles - - def get_legend_handles_labels(self, legend_handler_map=None): - """ - Return handles and labels for legend - - ``ax.legend()`` is equivalent to :: - - h, l = ax.get_legend_handles_labels() - ax.legend(h, l) - - """ - - handles = [] - labels = [] - for handle in self._get_legend_handles(legend_handler_map): - label = handle.get_label() - if label and not label.startswith('_'): - handles.append(handle) - labels.append(label) - - return handles, labels - - def legend(self, *args, **kwargs): - """ - Place a legend on the current axes. - - Call signature:: - - legend(*args, **kwargs) - - Places legend at location *loc*. Labels are a sequence of - strings and *loc* can be a string or an integer specifying the - legend location. - - To make a legend with existing lines:: - - legend() - - :meth:`legend` by itself will try and build a legend using the label - property of the lines/patches/collections. You can set the label of - a line by doing:: - - plot(x, y, label='my data') - - or:: - - line.set_label('my data'). - - If label is set to '_nolegend_', the item will not be shown in - legend. - - To automatically generate the legend from labels:: - - legend( ('label1', 'label2', 'label3') ) - - To make a legend for a list of lines and labels:: - - legend( (line1, line2, line3), ('label1', 'label2', 'label3') ) - - To make a legend at a given location, using a location argument:: - - legend( ('label1', 'label2', 'label3'), loc='upper left') - - or:: + - *c* is the 2*maxlags+1 auto correlation vector - legend((line1, line2, line3), ('label1', 'label2', 'label3'), loc=2) + - *line* is a :class:`~matplotlib.lines.Line2D` instance + returned by :meth:`plot` - The location codes are + The default *linestyle* is None and the default *marker* is + ``'o'``, though these can be overridden with keyword args. + The cross correlation is performed with + :func:`numpy.correlate` with *mode* = 2. - =============== ============= - Location String Location Code - =============== ============= - 'best' 0 - 'upper right' 1 - 'upper left' 2 - 'lower left' 3 - 'lower right' 4 - 'right' 5 - 'center left' 6 - 'center right' 7 - 'lower center' 8 - 'upper center' 9 - 'center' 10 - =============== ============= + If *usevlines* is *True*, :meth:`~matplotlib.axes.Axes.vlines` + rather than :meth:`~matplotlib.axes.Axes.plot` is used to draw + vertical lines from the origin to the acorr. Otherwise, the + plot style is determined by the kwargs, which are + :class:`~matplotlib.lines.Line2D` properties. + *maxlags* is a positive integer detailing the number of lags + to show. The default value of *None* will return all + ``(2*len(x)-1)`` lags. - Users can specify any arbitrary location for the legend using the - *bbox_to_anchor* keyword argument. bbox_to_anchor can be an instance - of BboxBase(or its derivatives) or a tuple of 2 or 4 floats. - For example:: + The return value is a tuple (*lags*, *c*, *linecol*, *b*) + where - loc = 'upper right', bbox_to_anchor = (0.5, 0.5) + - *linecol* is the + :class:`~matplotlib.collections.LineCollection` - will place the legend so that the upper right corner of the legend at - the center of the axes. + - *b* is the *x*-axis. - The legend location can be specified in other coordinate, by using the - *bbox_transform* keyword. + .. seealso:: - The loc itslef can be a 2-tuple giving x,y of the lower-left corner of - the legend in axes coords (*bbox_to_anchor* is ignored). + :meth:`~matplotlib.axes.Axes.plot` or + :meth:`~matplotlib.axes.Axes.vlines` + For documentation on valid kwargs. - Keyword arguments: + **Example:** - *prop*: [ *None* | FontProperties | dict ] - A :class:`matplotlib.font_manager.FontProperties` - instance. If *prop* is a dictionary, a new instance will be - created with *prop*. If *None*, use rc settings. + :func:`~matplotlib.pyplot.xcorr` is top graph, and + :func:`~matplotlib.pyplot.acorr` is bottom graph. - *fontsize*: [size in points | 'xx-small' | 'x-small' | 'small' | - 'medium' | 'large' | 'x-large' | 'xx-large'] - Set the font size. May be either a size string, relative to - the default font size, or an absolute font size in points. This - argument is only used if prop is not specified. + .. plot:: mpl_examples/pylab_examples/xcorr_demo.py + """ + return self.xcorr(x, x, **kwargs) - *numpoints*: integer - The number of points in the legend for line + @docstring.dedent_interpd + def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, + usevlines=True, maxlags=10, **kwargs): + """ + Plot the cross correlation between *x* and *y*. - *scatterpoints*: integer - The number of points in the legend for scatter plot + Call signature:: - *scatteryoffsets*: list of floats - a list of yoffsets for scatter symbols in legend + xcorr(self, x, y, normed=True, detrend=mlab.detrend_none, + usevlines=True, maxlags=10, **kwargs) - *markerscale*: [ *None* | scalar ] - The relative size of legend markers vs. original. If *None*, - use rc settings. + If *normed* = *True*, normalize the data by the cross + correlation at 0-th lag. *x* and y are detrended by the + *detrend* callable (default no normalization). *x* and *y* + must be equal length. - *frameon*: [ *True* | *False* ] - if *True*, draw a frame around the legend. - The default is set by the rcParam 'legend.frameon' + Data are plotted as ``plot(lags, c, **kwargs)`` - *fancybox*: [ *None* | *False* | *True* ] - if *True*, draw a frame with a round fancybox. If *None*, - use rc settings + Return value is a tuple (*lags*, *c*, *line*) where: - *shadow*: [ *None* | *False* | *True* ] - If *True*, draw a shadow behind legend. If *None*, - use rc settings. + - *lags* are a length ``2*maxlags+1`` lag vector - *framealpha*: [*None* | float] - If not None, alpha channel for legend frame. Default *None*. + - *c* is the ``2*maxlags+1`` auto correlation vector - *ncol* : integer - number of columns. default is 1 + - *line* is a :class:`~matplotlib.lines.Line2D` instance + returned by :func:`~matplotlib.pyplot.plot`. - *mode* : [ "expand" | *None* ] - if mode is "expand", the legend will be horizontally expanded - to fill the axes area (or *bbox_to_anchor*) + The default *linestyle* is *None* and the default *marker* is + 'o', though these can be overridden with keyword args. The + cross correlation is performed with :func:`numpy.correlate` + with *mode* = 2. - *bbox_to_anchor*: an instance of BboxBase or a tuple of 2 or 4 floats - the bbox that the legend will be anchored. + If *usevlines* is *True*: - *bbox_transform* : [ an instance of Transform | *None* ] - the transform for the bbox. transAxes if *None*. + :func:`~matplotlib.pyplot.vlines` + rather than :func:`~matplotlib.pyplot.plot` is used to draw + vertical lines from the origin to the xcorr. Otherwise the + plotstyle is determined by the kwargs, which are + :class:`~matplotlib.lines.Line2D` properties. - *title* : string - the legend title + The return value is a tuple (*lags*, *c*, *linecol*, *b*) + where *linecol* is the + :class:`matplotlib.collections.LineCollection` instance and + *b* is the *x*-axis. - Padding and spacing between various elements use following - keywords parameters. These values are measure in font-size - units. e.g., a fontsize of 10 points and a handlelength=5 - implies a handlelength of 50 points. Values from rcParams - will be used if None. + *maxlags* is a positive integer detailing the number of lags to show. + The default value of *None* will return all ``(2*len(x)-1)`` lags. - ================ ==================================================== - Keyword Description - ================ ==================================================== - borderpad the fractional whitespace inside the legend border - labelspacing the vertical space between the legend entries - handlelength the length of the legend handles - handletextpad the pad between the legend handle and text - borderaxespad the pad between the axes and legend border - columnspacing the spacing between columns - ================ ==================================================== + **Example:** - .. note:: + :func:`~matplotlib.pyplot.xcorr` is top graph, and + :func:`~matplotlib.pyplot.acorr` is bottom graph. - Not all kinds of artist are supported by the legend command. - See :ref:`plotting-guide-legend` for details. + .. plot:: mpl_examples/pylab_examples/xcorr_demo.py + """ - **Example:** + Nx = len(x) + if Nx != len(y): + raise ValueError('x and y must be equal length') - .. plot:: mpl_examples/api/legend_demo.py + x = detrend(np.asarray(x)) + y = detrend(np.asarray(y)) - .. seealso:: - :ref:`plotting-guide-legend`. + c = np.correlate(x, y, mode=2) - """ + if normed: + c /= np.sqrt(np.dot(x, x) * np.dot(y, y)) - if len(args) == 0: - handles, labels = self.get_legend_handles_labels() - if len(handles) == 0: - warnings.warn("No labeled objects found. " - "Use label='...' kwarg on individual plots.") - return None + if maxlags is None: + maxlags = Nx - 1 - elif len(args) == 1: - # LABELS - labels = args[0] - handles = [h for h, label in zip(self._get_legend_handles(), - labels)] + if maxlags >= Nx or maxlags < 1: + raise ValueError('maglags must be None or strictly ' + 'positive < %d' % Nx) - elif len(args) == 2: - if is_string_like(args[1]) or isinstance(args[1], int): - # LABELS, LOC - labels, loc = args - handles = [h for h, label in zip(self._get_legend_handles(), - labels)] - kwargs['loc'] = loc - else: - # LINES, LABELS - handles, labels = args + lags = np.arange(-maxlags, maxlags + 1) + c = c[Nx - 1 - maxlags:Nx + maxlags] - elif len(args) == 3: - # LINES, LABELS, LOC - handles, labels, loc = args - kwargs['loc'] = loc + if usevlines: + a = self.vlines(lags, [0], c, **kwargs) + b = self.axhline(**kwargs) else: - raise TypeError('Invalid arguments to legend') - - # Why do we need to call "flatten" here? -JJL - # handles = cbook.flatten(handles) - self.legend_ = mlegend.Legend(self, handles, labels, **kwargs) - return self.legend_ + kwargs.setdefault('marker', 'o') + kwargs.setdefault('linestyle', 'None') + a, = self.plot(lags, c, **kwargs) + b = None + return lags, c, a, b #### Specialized plotting @@ -4913,68 +4913,6 @@ def table(self, **kwargs): """ return mtable.table(self, **kwargs) - def _make_twin_axes(self, *kl, **kwargs): - """ - make a twinx axes of self. This is used for twinx and twiny. - """ - ax2 = self.figure.add_axes(self.get_position(True), *kl, **kwargs) - return ax2 - - def twinx(self): - """ - Call signature:: - - ax = twinx() - - create a twin of Axes for generating a plot with a sharex - x-axis but independent y axis. The y-axis of self will have - ticks on left and the returned axes will have ticks on the - right. - - .. note:: - For those who are 'picking' artists while using twinx, pick - events are only called for the artists in the top-most axes. - """ - - ax2 = self._make_twin_axes(sharex=self, frameon=False) - ax2.yaxis.tick_right() - ax2.yaxis.set_label_position('right') - ax2.yaxis.set_offset_position('right') - self.yaxis.tick_left() - ax2.xaxis.set_visible(False) - return ax2 - - def twiny(self): - """ - Call signature:: - - ax = twiny() - - create a twin of Axes for generating a plot with a shared - y-axis but independent x axis. The x-axis of self will have - ticks on bottom and the returned axes will have ticks on the - top. - - .. note:: - For those who are 'picking' artists while using twiny, pick - events are only called for the artists in the top-most axes. - """ - - ax2 = self._make_twin_axes(sharey=self, frameon=False) - ax2.xaxis.tick_top() - ax2.xaxis.set_label_position('top') - self.xaxis.tick_bottom() - ax2.yaxis.set_visible(False) - return ax2 - - def get_shared_x_axes(self): - 'Return a copy of the shared axes Grouper object for x axes' - return self._shared_x_axes - - def get_shared_y_axes(self): - 'Return a copy of the shared axes Grouper object for y axes' - return self._shared_y_axes - #### Data analysis @docstring.dedent_interpd @@ -6018,70 +5956,6 @@ def matshow(self, Z, **kwargs): integer=True)) return im - def get_default_bbox_extra_artists(self): - return [artist for artist in self.get_children() - if artist.get_visible()] - - def get_tightbbox(self, renderer, call_axes_locator=True): - """ - Return the tight bounding box of the axes. - The dimension of the Bbox in canvas coordinate. - - If *call_axes_locator* is *False*, it does not call the - _axes_locator attribute, which is necessary to get the correct - bounding box. ``call_axes_locator==False`` can be used if the - caller is only intereted in the relative size of the tightbbox - compared to the axes bbox. - """ - - bb = [] - - if not self.get_visible(): - return None - - locator = self.get_axes_locator() - if locator and call_axes_locator: - pos = locator(self, renderer) - self.apply_aspect(pos) - else: - self.apply_aspect() - - bb.append(self.get_window_extent(renderer)) - - if self.title.get_visible(): - bb.append(self.title.get_window_extent(renderer)) - if self._left_title.get_visible(): - bb.append(self._left_title.get_window_extent(renderer)) - if self._right_title.get_visible(): - bb.append(self._right_title.get_window_extent(renderer)) - - bb_xaxis = self.xaxis.get_tightbbox(renderer) - if bb_xaxis: - bb.append(bb_xaxis) - - bb_yaxis = self.yaxis.get_tightbbox(renderer) - if bb_yaxis: - bb.append(bb_yaxis) - - _bbox = mtransforms.Bbox.union( - [b for b in bb if b.width != 0 or b.height != 0]) - - return _bbox - - def minorticks_on(self): - 'Add autoscaling minor ticks to the axes.' - for ax in (self.xaxis, self.yaxis): - if ax.get_scale() == 'log': - s = ax._scale - ax.set_minor_locator(mticker.LogLocator(s.base, s.subs)) - else: - ax.set_minor_locator(mticker.AutoMinorLocator()) - - def minorticks_off(self): - """Remove minor ticks from the axes.""" - self.xaxis.set_minor_locator(mticker.NullLocator()) - self.yaxis.set_minor_locator(mticker.NullLocator()) - def tricontour(self, *args, **kwargs): return mtri.tricontour(self, *args, **kwargs) tricontour.__doc__ = mtri.TriContourSet.tricontour_doc diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index b4cead3ad71d..4aa64af2d1f2 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -16,6 +16,7 @@ import matplotlib.patches as mpatches import matplotlib.artist as martist import matplotlib.transforms as mtransforms +import matplotlib.ticker as mticker import matplotlib.axis as maxis import matplotlib.scale as mscale import matplotlib.spines as mspines @@ -700,6 +701,8 @@ def get_yaxis_text2_transform(self, pad_points): self.figure.dpi_scale_trans), "center", "left") + + def _update_transScale(self): self.transScale.set( mtransforms.blended_transform_factory( @@ -2732,9 +2735,10 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): top = old_top if bottom == top: - warnings.warn(('Attempting to set identical bottom==top results\n' - + 'in singular transformations; automatically expanding.\n' - + 'bottom=%s, top=%s') % (bottom, top)) + warnings.warn( + ('Attempting to set identical bottom==top results\n' + 'in singular transformations; automatically expanding.\n' + 'bottom=%s, top=%s') % (bottom, top)) bottom, top = mtransforms.nonsingular(bottom, top, increasing=False) bottom, top = self.yaxis.limit_range_for_scale(bottom, top) @@ -2751,7 +2755,7 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): other.set_ylim(self.viewLim.intervaly, emit=False, auto=auto) if (other.figure != self.figure and - other.figure.canvas is not None): + other.figure.canvas is not None): other.figure.canvas.draw_idle() return bottom, top @@ -2897,6 +2901,20 @@ def format_coord(self, x, y): ys = self.format_ydata(y) return 'x=%s y=%s' % (xs, ys) + def minorticks_on(self): + 'Add autoscaling minor ticks to the axes.' + for ax in (self.xaxis, self.yaxis): + if ax.get_scale() == 'log': + s = ax._scale + ax.set_minor_locator(mticker.LogLocator(s.base, s.subs)) + else: + ax.set_minor_locator(mticker.AutoMinorLocator()) + + def minorticks_off(self): + """Remove minor ticks from the axes.""" + self.xaxis.set_minor_locator(mticker.NullLocator()) + self.yaxis.set_minor_locator(mticker.NullLocator()) + #### Interactive manipulation def can_zoom(self): @@ -3128,3 +3146,117 @@ def pick(self, *args): the artist and the artist has picker set """ martist.Artist.pick(self, args[0]) + + def get_default_bbox_extra_artists(self): + return [artist for artist in self.get_children() + if artist.get_visible()] + + def get_tightbbox(self, renderer, call_axes_locator=True): + """ + Return the tight bounding box of the axes. + The dimension of the Bbox in canvas coordinate. + + If *call_axes_locator* is *False*, it does not call the + _axes_locator attribute, which is necessary to get the correct + bounding box. ``call_axes_locator==False`` can be used if the + caller is only intereted in the relative size of the tightbbox + compared to the axes bbox. + """ + + bb = [] + + if not self.get_visible(): + return None + + locator = self.get_axes_locator() + if locator and call_axes_locator: + pos = locator(self, renderer) + self.apply_aspect(pos) + else: + self.apply_aspect() + + bb.append(self.get_window_extent(renderer)) + + if self.title.get_visible(): + bb.append(self.title.get_window_extent(renderer)) + if self._left_title.get_visible(): + bb.append(self._left_title.get_window_extent(renderer)) + if self._right_title.get_visible(): + bb.append(self._right_title.get_window_extent(renderer)) + + bb_xaxis = self.xaxis.get_tightbbox(renderer) + if bb_xaxis: + bb.append(bb_xaxis) + + bb_yaxis = self.yaxis.get_tightbbox(renderer) + if bb_yaxis: + bb.append(bb_yaxis) + + _bbox = mtransforms.Bbox.union( + [b for b in bb if b.width != 0 or b.height != 0]) + + return _bbox + + def _make_twin_axes(self, *kl, **kwargs): + """ + make a twinx axes of self. This is used for twinx and twiny. + """ + ax2 = self.figure.add_axes(self.get_position(True), *kl, **kwargs) + return ax2 + + def twinx(self): + """ + Call signature:: + + ax = twinx() + + create a twin of Axes for generating a plot with a sharex + x-axis but independent y axis. The y-axis of self will have + ticks on left and the returned axes will have ticks on the + right. + + .. note:: + For those who are 'picking' artists while using twinx, pick + events are only called for the artists in the top-most axes. + """ + + ax2 = self._make_twin_axes(sharex=self, frameon=False) + ax2.yaxis.tick_right() + ax2.yaxis.set_label_position('right') + ax2.yaxis.set_offset_position('right') + self.yaxis.tick_left() + ax2.xaxis.set_visible(False) + return ax2 + + def twiny(self): + """ + Call signature:: + + ax = twiny() + + create a twin of Axes for generating a plot with a shared + y-axis but independent x axis. The x-axis of self will have + ticks on bottom and the returned axes will have ticks on the + top. + + .. note:: + For those who are 'picking' artists while using twiny, pick + events are only called for the artists in the top-most axes. + """ + + ax2 = self._make_twin_axes(sharey=self, frameon=False) + ax2.xaxis.tick_top() + ax2.xaxis.set_label_position('top') + self.xaxis.tick_bottom() + ax2.yaxis.set_visible(False) + return ax2 + + def get_shared_x_axes(self): + 'Return a copy of the shared axes Grouper object for x axes' + return self._shared_x_axes + + def get_shared_y_axes(self): + 'Return a copy of the shared axes Grouper object for y axes' + return self._shared_y_axes + + From 94cb09b26f8323be250f589ba5baa0e99094dfc4 Mon Sep 17 00:00:00 2001 From: Nelle Varoquaux Date: Sat, 29 Jun 2013 16:36:53 +0200 Subject: [PATCH 7/8] FIX importing _string_to_bool from the wrong module --- lib/matplotlib/axes/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/__init__.py b/lib/matplotlib/axes/__init__.py index ab0a39e72076..018398edcb67 100644 --- a/lib/matplotlib/axes/__init__.py +++ b/lib/matplotlib/axes/__init__.py @@ -1,3 +1,3 @@ from matplotlib.axes._subplots import * from matplotlib.axes._axes import * -from matplotlib.axes._axes import _string_to_bool +from matplotlib.axes._base import _string_to_bool From 9412e84837cb39107a0330a8877ce126bec2b0c0 Mon Sep 17 00:00:00 2001 From: Nelle Varoquaux Date: Sat, 29 Jun 2013 18:28:07 +0200 Subject: [PATCH 8/8] DOC - added the differences between the axes module namespace before and after --- doc/api/api_changes.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index 11bfaffbd607..a17ef4971e52 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -23,6 +23,30 @@ splitted into smaller modules: from _AxesBase, and contains all plotting and labelling methods. - the `_subplot` module, with all the classes concerning subplotting. +There are a couple of things that do not exists in the `axes` module's +namespace anymore. If you use them, you need to import them from their +original location: + + - math -> `import math` + - ma -> `from numpy import ma` + - cbook -> `from matplotlib import cbook` + - division -> `from __future__ import division` + - docstring -> `from matplotlib impotr docstring` + - is_sequence_of_strings -> `from matplotlib.cbook import is_sequence_of_strings` + - is_string_like -> `from matplotlib.cbook import is_string_like` + - iterable -> `from matplotlib.cbook import iterable` + - itertools -> `import itertools` + - martist -> `from matplotlib import artist as martist` + - matplotlib -> `import matplotlib` + - mcoll -> `from matplotlib import collections as mcoll` + - mcolors -> `from matplotlib import colors as mcolors` + - mcontour -> `from matplotlib import contour as mcontour` + - mpatches -> `from matplotlib import patchs as mpatches` + - mpath -> `from matplotlib import path as mpath` + - mquiver -> `from matplotlib import quiver as mquiver` + - mstack -> `from matplotlib import stack as mstack` + - mstream -> `from matplotlib import stream as mstream` + - mtable -> `from matplotlib import table as mtable` .. _changes_in_1_3: