diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 4462cd620ce6..674f56639e5a 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -438,7 +438,7 @@ def __init__(self, fig, rect, ================ ========================================= Keyword Description ================ ========================================= - *adjustable* [ 'box' | 'datalim' | 'box-forced'] + *adjustable* [ 'box' | 'datalim' ] *alpha* float: the alpha transparency (can be None) *anchor* [ 'C', 'SW', 'S', 'SE', 'E', 'NE', 'N', 'NW', 'W' ] @@ -488,25 +488,15 @@ def __init__(self, fig, rect, self._originalPosition = self._position.frozen() # self.set_axes(self) self.axes = self - self.set_aspect('auto') + self._aspect = 'auto' self._adjustable = 'box' - self.set_anchor('C') + self._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) @@ -537,7 +527,11 @@ def __init__(self, fig, rect, self._hold = True self._connected = {} # a dict from events to (id, func) - self.cla() + try: + self.cla(clear_axis=False) # new xaxis, yaxis are already cleared + except TypeError: + self.cla() # For Axes subclasses lacking clear_axis argument. + # funcs used to format x and y - fall back on major formatters self.fmt_xdata = None self.fmt_ydata = None @@ -607,11 +601,11 @@ def get_window_extent(self, *args, **kwargs): 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.spines['bottom'].register_axis(self.xaxis, clear_axis=False) + self.spines['top'].register_axis(self.xaxis, clear_axis=False) self.yaxis = maxis.YAxis(self) - self.spines['left'].register_axis(self.yaxis) - self.spines['right'].register_axis(self.yaxis) + self.spines['left'].register_axis(self.yaxis, clear_axis=False) + self.spines['right'].register_axis(self.yaxis, clear_axis=False) self._update_transScale() def set_figure(self, fig): @@ -634,8 +628,7 @@ def set_figure(self, fig): def _set_lim_and_transforms(self): """ - set the *dataLim* and *viewLim* - :class:`~matplotlib.transforms.Bbox` attributes and the + set the *_xaxis_transform*, *_yaxis_transform*, *transScale*, *transData*, *transLimits* and *transAxes* transformations. @@ -952,7 +945,7 @@ def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'): ('bottom', mspines.Spine.linear_spine(self, 'bottom')), ('top', mspines.Spine.linear_spine(self, 'top'))]) - def cla(self): + def cla(self, clear_axis=True): """Clear the current axes.""" # Note: this is called by Axes.__init__() @@ -965,51 +958,46 @@ def cla(self): xaxis_visible = self.xaxis.get_visible() yaxis_visible = self.yaxis.get_visible() - self.xaxis.cla() - self.yaxis.cla() + # The axis cla() sets the scale and default locators and + # formatters. It needs to know if the axis is shared + # so that it can preserve the shared scale. + shared_x = self._sharex.xaxis if self._sharex else None + shared_y = self._sharey.yaxis if self._sharey else None + + if clear_axis: + self.xaxis.cla(shared_x) + self.yaxis.cla(shared_y) + for name, spine in six.iteritems(self.spines): - spine.cla() + spine.cla(clear_axis=False) # Clears only the position. self.ignore_existing_data_limits = True self.callbacks = cbook.CallbackRegistry() - if self._sharex is not None: - # major and minor are class instances with + if shared_x is not None: + # major and minor are axis.Ticker class instances with # locator and formatter attributes - self.xaxis.major = self._sharex.xaxis.major - self.xaxis.minor = self._sharex.xaxis.minor + self.xaxis.major = shared_x.major + self.xaxis.minor = shared_x.minor x0, x1 = self._sharex.get_xlim() self.set_xlim(x0, x1, emit=False, auto=None) - self.xaxis._scale = mscale.scale_factory( - self._sharex.xaxis.get_scale(), self.xaxis) else: - self.xaxis._set_scale('linear') try: self.set_xlim(0, 1) except TypeError: pass - if self._sharey is not None: - self.yaxis.major = self._sharey.yaxis.major - self.yaxis.minor = self._sharey.yaxis.minor + if shared_y is not None: + self.yaxis.major = shared_y.major + self.yaxis.minor = shared_y.minor y0, y1 = self._sharey.get_ylim() self.set_ylim(y0, y1, emit=False, auto=None) - self.yaxis._scale = mscale.scale_factory( - self._sharey.yaxis.get_scale(), self.yaxis) else: - self.yaxis._set_scale('linear') try: self.set_ylim(0, 1) except TypeError: pass - # update the minor locator for x and y axis based on rcParams - if (rcParams['xtick.minor.visible']): - self.xaxis.set_minor_locator(mticker.AutoMinorLocator()) - - if (rcParams['ytick.minor.visible']): - self.yaxis.set_minor_locator(mticker.AutoMinorLocator()) - self._autoscaleXon = True self._autoscaleYon = True self._xmargin = rcParams['axes.xmargin'] @@ -1093,6 +1081,13 @@ def cla(self): self.yaxis.set_visible(yaxis_visible) self.patch.set_visible(patch_visible) + # It is not clear to me (EF) why this reset is needed here, but + # it does seem to be needed somewhere in this vicinity. Otherwise, + # setting tick rotation via set_params doesn't work until the + # first draw has occurred. + self.xaxis.reset_ticks() + self.yaxis.reset_ticks() + self.stale = True @cbook.deprecated("2.1", alternative="Axes.patch") @@ -1224,7 +1219,7 @@ def hold(self, b=None): def get_aspect(self): return self._aspect - def set_aspect(self, aspect, adjustable=None, anchor=None): + def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): """ *aspect* @@ -1245,12 +1240,9 @@ def set_aspect(self, aspect, adjustable=None, anchor=None): ============ ===================================== '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'. + When both axes are shared, only 'box' is allowable. *anchor* @@ -1270,8 +1262,17 @@ def set_aspect(self, aspect, adjustable=None, anchor=None): else: self._aspect = float(aspect) # raise ValueError if necessary - if adjustable is not None: - self.set_adjustable(adjustable) + if share and self in self._shared_x_axes: + for ax in self._shared_x_axes.get_siblings(self): + ax._aspect = aspect + if share and self in self._shared_y_axes: + for ax in self._shared_y_axes.get_siblings(self): + ax._aspect = aspect + + if adjustable is None: + adjustable = self._adjustable + self.set_adjustable(adjustable, share=share) # Handle sharing. + if anchor is not None: self.set_anchor(anchor) self.stale = True @@ -1279,15 +1280,18 @@ def set_aspect(self, aspect, adjustable=None, anchor=None): def get_adjustable(self): return self._adjustable - def set_adjustable(self, adjustable): + def set_adjustable(self, adjustable, share=False): """ - ACCEPTS: [ 'box' | 'datalim' | 'box-forced'] + ACCEPTS: [ 'box' | 'datalim'] """ + # FIXME: add box-forced deprecation 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') + if share and self in self._shared_x_axes: + for ax in self._shared_x_axes.get_siblings(self): + ax._adjustable = adjustable + if share and self in self._shared_y_axes: + for ax in self._shared_y_axes.get_siblings(self): + ax._adjustable = adjustable self._adjustable = adjustable else: raise ValueError('argument must be "box", or "datalim"') @@ -1387,14 +1391,6 @@ def apply_aspect(self, position=None): 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']: @@ -1452,15 +1448,15 @@ def apply_aspect(self, position=None): xm = 0 ym = 0 - 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: + shared_x = self in self._shared_x_axes + shared_y = self in self._shared_y_axes + # Not sure whether we need this check: + if shared_x and shared_y: + raise RuntimeError("adjustable='datalim' is not allowed when both" + " axes are shared.") + + # If y is shared, then we are only allowed to change x, etc. + if shared_y: adjust_y = False else: if xmarg > xm and ymarg > ym: @@ -1468,7 +1464,8 @@ def apply_aspect(self, position=None): (Xmarg < 0 and y_expander > 0)) else: adjy = y_expander > 0 - adjust_y = changey or adjy # (Ymarg > xmarg) + adjust_y = shared_x or adjy # (Ymarg > xmarg) + if adjust_y: yc = 0.5 * (ymin + ymax) y0 = yc - Ysize / 2.0 @@ -3915,6 +3912,9 @@ 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) + # do not touch every thing shared, just this and it's twin. + self.set_adjustable('datalim') + ax2.set_adjustable('datalim') return ax2 def twinx(self): @@ -3977,9 +3977,9 @@ def twiny(self): return ax2 def get_shared_x_axes(self): - 'Return a copy of the shared axes Grouper object for x axes' + 'Return a reference to 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 a reference to the shared axes Grouper object for y axes' return self._shared_y_axes diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 870c5e8f6f72..91930dd9611a 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -24,6 +24,14 @@ GRIDLINE_INTERPOLATION_STEPS = 180 +# This list is being used for compatibility with Axes.grid, which +# allows all Line2D kwargs. +_line_AI = artist.ArtistInspector(mlines.Line2D) +_line_param_names = _line_AI.get_setters() +_line_param_aliases = [list(d.keys())[0] for d in _line_AI.aliasd.values()] +_gridline_param_names = ['grid_' + name + for name in _line_param_names + _line_param_aliases] + class Tick(artist.Artist): """ @@ -82,6 +90,11 @@ def __init__(self, axes, loc, label, label2On=False, major=True, labelrotation=0, + grid_color=None, + grid_linestyle=None, + grid_linewidth=None, + grid_alpha=None, + **kw # Other Line2D kwargs applied to gridlines. ): """ bbox is the Bound2D bounding box in display coords of the Axes @@ -149,6 +162,17 @@ def __init__(self, axes, loc, label, zorder = mlines.Line2D.zorder self._zorder = zorder + self._grid_color = (rcParams['grid.color'] + if grid_color is None else grid_color) + self._grid_linestyle = (rcParams['grid.linestyle'] + if grid_linestyle is None else grid_linestyle) + self._grid_linewidth = (rcParams['grid.linewidth'] + if grid_linewidth is None else grid_linewidth) + self._grid_alpha = (rcParams['grid.alpha'] + if grid_alpha is None else grid_alpha) + + self._grid_kw = {k[5:]: v for k, v in kw.items()} + self.apply_tickdir(tickdir) self.tick1line = self._get_tick1line() @@ -344,6 +368,14 @@ def _apply_params(self, **kw): v = getattr(self.label1, 'get_' + k)() setattr(self, '_label' + k, v) + grid_list = [k for k in six.iteritems(kw) + if k[0] in _gridline_param_names] + if grid_list: + grid_kw = {k[5:]: v for k, v in grid_list} + self.gridline.set(**grid_kw) + for k, v in six.iteritems(grid_kw): + setattr(self, '_grid_' + k, v) + def update_position(self, loc): 'Set the location of tick in data coords with scalar *loc*' raise NotImplementedError('Derived must override') @@ -445,11 +477,12 @@ def _get_gridline(self): 'Get the default line2D instance' # x in data coords, y in axes coords l = mlines.Line2D(xdata=(0.0, 0.0), ydata=(0, 1.0), - color=rcParams['grid.color'], - linestyle=rcParams['grid.linestyle'], - linewidth=rcParams['grid.linewidth'], - alpha=rcParams['grid.alpha'], - markersize=0) + color=self._grid_color, + linestyle=self._grid_linestyle, + linewidth=self._grid_linewidth, + alpha=self._grid_alpha, + markersize=0, + **self._grid_kw) l.set_transform(self.axes.get_xaxis_transform(which='grid')) l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS self._set_artist_props(l) @@ -568,12 +601,12 @@ def _get_gridline(self): 'Get the default line2D instance' # x in axes coords, y in data coords l = mlines.Line2D(xdata=(0, 1), ydata=(0, 0), - color=rcParams['grid.color'], - linestyle=rcParams['grid.linestyle'], - linewidth=rcParams['grid.linewidth'], - alpha=rcParams['grid.alpha'], - markersize=0) - + color=self._grid_color, + linestyle=self._grid_linestyle, + linewidth=self._grid_linewidth, + alpha=self._grid_alpha, + markersize=0, + **self._grid_kw) l.set_transform(self.axes.get_yaxis_transform(which='grid')) l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS self._set_artist_props(l) @@ -626,13 +659,6 @@ def __init__(self, axes, pickradius=15): artist.Artist.__init__(self) self.set_figure(axes.figure) - # Keep track of setting to the default value, this allows use to know - # if any of the following values is explicitly set by the user, so as - # to not overwrite their settings with any of our 'auto' settings. - self.isDefault_majloc = True - self.isDefault_minloc = True - self.isDefault_majfmt = True - self.isDefault_minfmt = True self.isDefault_label = True self.axes = axes @@ -656,7 +682,6 @@ def __init__(self, axes, pickradius=15): self._minor_tick_kw = dict() self.cla() - self._set_scale('linear') def set_label_coords(self, x, y, transform=None): """ @@ -719,24 +744,16 @@ def get_children(self): children.extend(minorticks) return children - def cla(self): + def cla(self, shared=None): 'clear the current axis' - self.set_major_locator(mticker.AutoLocator()) - self.set_major_formatter(mticker.ScalarFormatter()) - self.set_minor_locator(mticker.NullLocator()) - self.set_minor_formatter(mticker.NullFormatter()) - self.set_label_text('') - self._set_artist_props(self.label) + self.label.set_text('') # self.set_label_text would change isDefault_ - # Keep track of setting to the default value, this allows use to know - # if any of the following values is explicitly set by the user, so as - # to not overwrite their settings with any of our 'auto' settings. - self.isDefault_majloc = True - self.isDefault_minloc = True - self.isDefault_majfmt = True - self.isDefault_minfmt = True - self.isDefault_label = True + if shared is None: + self._set_scale('linear') + else: + name = shared.get_scale() + self._scale = mscale.scale_factory(name, self) # Clear the callback registry for this axis, or it may "leak" self.callbacks = cbook.CallbackRegistry() @@ -747,9 +764,6 @@ def cla(self): self._gridOnMinor = (rcParams['axes.grid'] and rcParams['axes.grid.which'] in ('both', 'minor')) - self.label.set_text('') - self._set_artist_props(self.label) - self.reset_ticks() self.converter = None @@ -758,9 +772,11 @@ def cla(self): self.stale = True def reset_ticks(self): - # build a few default ticks; grow as necessary later; only - # define 1 so properties set on ticks will be copied as they - # grow + """ + Re-initialize the major and minor Tick lists. + + Each list starts with a single fresh Tick. + """ del self.majorTicks[:] del self.minorTicks[:] @@ -769,6 +785,11 @@ def reset_ticks(self): self._lastNumMajorTicks = 1 self._lastNumMinorTicks = 1 + try: + self.set_clip_path(self.axes.patch) + except AttributeError: + pass + def set_tick_params(self, which='major', reset=False, **kw): """ Set appearance parameters for ticks and ticklabels. @@ -786,6 +807,7 @@ def set_tick_params(self, which='major', reset=False, **kw): if reset: d.clear() d.update(kwtrans) + if reset: self.reset_ticks() else: @@ -809,7 +831,8 @@ def _translate_tick_kw(kw, to_init_kw=True): kwkeys1 = ['length', 'direction', 'left', 'bottom', 'right', 'top', 'labelleft', 'labelbottom', 'labelright', 'labeltop', 'rotation'] - kwkeys = kwkeys0 + kwkeys1 + kwkeys2 = _gridline_param_names + kwkeys = kwkeys0 + kwkeys1 + kwkeys2 kwtrans = dict() if to_init_kw: if 'length' in kw: @@ -951,7 +974,7 @@ def _update_ticks(self, renderer): """ interval = self.get_view_interval() - tick_tups = list(self.iter_ticks()) + tick_tups = list(self.iter_ticks()) # iter_ticks calls the locator if self._smart_bounds and tick_tups: # handle inverted limits view_low, view_high = sorted(interval) @@ -1376,30 +1399,21 @@ def grid(self, b=None, which='major', **kwargs): if len(kwargs): b = True which = which.lower() + gridkw = {'grid_' + item[0]: item[1] for item in kwargs.items()} if which in ['minor', 'both']: if b is None: self._gridOnMinor = not self._gridOnMinor else: self._gridOnMinor = b - for tick in self.minorTicks: # don't use get_ticks here! - if tick is None: - continue - tick.gridOn = self._gridOnMinor - if len(kwargs): - tick.gridline.update(kwargs) - self._minor_tick_kw['gridOn'] = self._gridOnMinor + self.set_tick_params(which='minor', gridOn=self._gridOnMinor, + **gridkw) if which in ['major', 'both']: if b is None: self._gridOnMajor = not self._gridOnMajor else: self._gridOnMajor = b - for tick in self.majorTicks: # don't use get_ticks here! - if tick is None: - continue - tick.gridOn = self._gridOnMajor - if len(kwargs): - tick.gridline.update(kwargs) - self._major_tick_kw['gridOn'] = self._gridOnMajor + self.set_tick_params(which='major', gridOn=self._gridOnMajor, + **gridkw) self.stale = True def update_units(self, data): @@ -1429,11 +1443,11 @@ def _update_axisinfo(self): check the axis converter for the stored units to see if the axis info needs to be updated """ - if self.converter is None: return info = self.converter.axisinfo(self.units, self) + if info is None: return if info.majloc is not None and \ diff --git a/lib/matplotlib/scale.py b/lib/matplotlib/scale.py index 29a38b6663c2..e1df53b97a04 100644 --- a/lib/matplotlib/scale.py +++ b/lib/matplotlib/scale.py @@ -10,9 +10,10 @@ from matplotlib.ticker import (NullFormatter, ScalarFormatter, LogFormatterSciNotation, LogitFormatter) from matplotlib.ticker import (NullLocator, LogLocator, AutoLocator, + AutoMinorLocator, SymmetricalLogLocator, LogitLocator) from matplotlib.transforms import Transform, IdentityTransform -from matplotlib import docstring +from matplotlib import docstring, rcParams class ScaleBase(object): @@ -73,8 +74,12 @@ def set_default_locators_and_formatters(self, axis): """ axis.set_major_locator(AutoLocator()) axis.set_major_formatter(ScalarFormatter()) - axis.set_minor_locator(NullLocator()) axis.set_minor_formatter(NullFormatter()) + # update the minor locator for x and y axis based on rcParams + if rcParams['xtick.minor.visible']: + axis.set_minor_locator(AutoMinorLocator()) + else: + axis.set_minor_locator(NullLocator()) def get_transform(self): """ diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index c6b04fe43dba..de5c294ad856 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -151,7 +151,7 @@ def _ensure_position_is_set(self): self._position = ('outward', 0.0) # in points self.set_position(self._position) - def register_axis(self, axis): + def register_axis(self, axis, clear_axis=True): """register an axis An axis should be registered with its corresponding spine from @@ -159,14 +159,14 @@ def register_axis(self, axis): properties when needed. """ self.axis = axis - if self.axis is not None: + if clear_axis and self.axis is not None: self.axis.cla() self.stale = True - def cla(self): + def cla(self, clear_axis=True): """Clear the current spine""" self._position = None # clear position - if self.axis is not None: + if clear_axis and self.axis is not None: self.axis.cla() def is_frame_like(self): diff --git a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.pdf b/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.pdf index 209842345b46..c16fc9c2d916 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.pdf and b/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.png b/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.png index 9abe25a233cd..7af54d6eefbf 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.png and b/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.svg b/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.svg index 668efde6a8ad..ff18e2b4df0b 100644 --- a/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.svg +++ b/lib/matplotlib/tests/baseline_images/test_skew/skew_rects.svg @@ -2,7 +2,7 @@ - + - - - - - - - @@ -60,188 +60,188 @@ L 230.4 86.4 +" id="m368fc901b1" style="stroke:#000000;stroke-width:0.5;"/> - + +" id="mc63e59a608" style="stroke:#000000;stroke-width:0.5;"/> - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + - + + + + - + - + - + - + - - - + - - - - + - - - - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -249,209 +249,209 @@ L -4 0 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -459,209 +459,209 @@ L 460.8 86.4 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -669,209 +669,209 @@ L 691.2 86.4 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -879,209 +879,209 @@ L 921.6 86.4 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1089,209 +1089,209 @@ L 1152 86.4 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1299,209 +1299,209 @@ L 230.4 247.282759 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1509,209 +1509,209 @@ L 460.8 247.282759 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1719,209 +1719,209 @@ L 691.2 247.282759 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1929,209 +1929,209 @@ L 921.6 247.282759 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2139,209 +2139,209 @@ L 1152 247.282759 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2349,209 +2349,209 @@ L 230.4 408.165517 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2559,209 +2559,209 @@ L 460.8 408.165517 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2769,209 +2769,209 @@ L 691.2 408.165517 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2979,209 +2979,209 @@ L 921.6 408.165517 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3189,209 +3189,209 @@ L 1152 408.165517 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3399,209 +3399,209 @@ L 230.4 569.048276 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3609,209 +3609,209 @@ L 460.8 569.048276 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3819,209 +3819,209 @@ L 691.2 569.048276 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4029,209 +4029,209 @@ L 921.6 569.048276 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4239,209 +4239,209 @@ L 1152 569.048276 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4449,209 +4449,209 @@ L 230.4 729.931034 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4659,209 +4659,209 @@ L 460.8 729.931034 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -4869,209 +4869,209 @@ L 691.2 729.931034 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -5079,209 +5079,209 @@ L 921.6 729.931034 - - - - - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + - + - + - + - + - - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -5289,80 +5289,80 @@ L 1152 729.931034 - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index f1cc67fde7a4..14ef9f04fdbe 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4220,6 +4220,57 @@ def test_empty_shared_subplots(): assert y1 >= 6 +def test_shared_with_aspect(): + # allow sharing one axis + for adjustable in ['box', 'datalim']: + fig, axes = plt.subplots(nrows=2, sharex=True) + axes[0].set_aspect(2, adjustable=adjustable, share=True) + assert axes[1].get_aspect() == 2 + assert axes[1].get_adjustable() == adjustable + + fig, axes = plt.subplots(nrows=2, sharex=True) + axes[0].set_aspect(2, adjustable=adjustable) + assert axes[1].get_aspect() == 'auto' + + # Share 2 axes only with 'box': + fig, axes = plt.subplots(nrows=2, sharex=True, sharey=True) + axes[0].set_aspect(2, share=True) + axes[0].plot([1, 2], [3, 4]) + axes[1].plot([3, 4], [1, 2]) + plt.draw() # Trigger apply_aspect(). + assert axes[0].get_xlim() == axes[1].get_xlim() + assert axes[0].get_ylim() == axes[1].get_ylim() + + # Different aspect ratios: + for adjustable in ['box', 'datalim']: + fig, axes = plt.subplots(nrows=2, sharey=True) + axes[0].set_aspect(2, adjustable=adjustable) + axes[1].set_aspect(0.5, adjustable=adjustable) + axes[0].plot([1, 2], [3, 4]) + axes[1].plot([3, 4], [1, 2]) + plt.draw() # Trigger apply_aspect(). + assert axes[0].get_xlim() != axes[1].get_xlim() + assert axes[0].get_ylim() == axes[1].get_ylim() + fig_aspect = fig.bbox_inches.height / fig.bbox_inches.width + for ax in axes: + p = ax.get_position() + box_aspect = p.height / p.width + lim_aspect = ax.viewLim.height / ax.viewLim.width + expected = fig_aspect * box_aspect / lim_aspect + assert round(expected, 4) == round(ax.get_aspect(), 4) + + +@pytest.mark.parametrize('twin', ('x', 'y')) +def test_twin_with_aspect(twin): + fig, ax = plt.subplots() + # test twinx or twiny + ax_twin = getattr(ax, 'twin{}'.format(twin))() + ax.set_aspect(5) + ax_twin.set_aspect(2) + assert_array_equal(ax.bbox.extents, + ax_twin.bbox.extents) + + def test_relim_visible_only(): x1 = (0., 10.) y1 = (0., 10.) diff --git a/lib/matplotlib/tests/test_skew.py b/lib/matplotlib/tests/test_skew.py index 79cb75c2c273..a80d3cdaf002 100644 --- a/lib/matplotlib/tests/test_skew.py +++ b/lib/matplotlib/tests/test_skew.py @@ -189,16 +189,16 @@ def test_set_line_coll_dash_image(): @image_comparison(baseline_images=['skew_rects'], remove_text=True) -def test_skew_rectange(): +def test_skew_rectangle(): - fix, axes = plt.subplots(5, 5, sharex=True, sharey=True, figsize=(16, 12)) + fix, axes = plt.subplots(5, 5, sharex=True, sharey=True, figsize=(8, 8)) axes = axes.flat rotations = list(itertools.product([-3, -1, 0, 1, 3], repeat=2)) - axes[0].set_xlim([-4, 4]) - axes[0].set_ylim([-4, 4]) - axes[0].set_aspect('equal') + axes[0].set_xlim([-3, 3]) + axes[0].set_ylim([-3, 3]) + axes[0].set_aspect('equal', share=True) for ax, (xrots, yrots) in zip(axes, rotations): xdeg, ydeg = 45 * xrots, 45 * yrots @@ -209,4 +209,4 @@ def test_skew_rectange(): transform=t + ax.transData, alpha=0.5, facecolor='coral')) - plt.subplots_adjust(wspace=0, left=0, right=1, bottom=0) + plt.subplots_adjust(wspace=0, left=0.01, right=0.99, bottom=0.01, top=0.99)