diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 6c0556b20fdb..291a8b6c1eaf 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -414,6 +414,7 @@ class _AxesBase(martist.Artist): _shared_x_axes = cbook.Grouper() _shared_y_axes = cbook.Grouper() + _twinned_axes = cbook.Grouper() def __str__(self): return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format( @@ -440,7 +441,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 +489,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) @@ -530,6 +521,7 @@ def __init__(self, fig, rect, 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 @@ -630,8 +622,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. @@ -881,10 +872,11 @@ def set_position(self, pos, which='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) + for ax in self._twinned_axes.get_siblings(self): + if which in ('both', 'active'): + ax._position.set(pos) + if which in ('both', 'original'): + ax._originalPosition.set(pos) self.stale = True def reset_position(self): @@ -894,8 +886,9 @@ def reset_position(self): This resets the a possible position change due to aspect constraints. For an explanation of the positions see `.set_position`. """ - pos = self.get_position(original=True) - self.set_position(pos, which='active') + for ax in self._twinned_axes.get_siblings(self): + pos = ax.get_position(original=True) + ax.set_position(pos, which='active') def set_axes_locator(self, locator): """ @@ -980,6 +973,7 @@ def cla(self): self.xaxis.cla() self.yaxis.cla() + for name, spine in six.iteritems(self.spines): spine.cla() @@ -987,7 +981,7 @@ def cla(self): self.callbacks = cbook.CallbackRegistry() if self._sharex is not None: - # major and minor are class instances with + # 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 @@ -1015,7 +1009,6 @@ def cla(self): 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()) @@ -1257,7 +1250,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): """ Set the aspect of the axis scaling, i.e. the ratio of y-unit to x-unit. @@ -1276,7 +1269,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None): aspect='equal'. ======== ================================================ - adjustable : None or ['box' | 'datalim' | 'box-forced'], optional + adjustable : None or ['box' | 'datalim'], optional If not ``None``, this defines which parameter will be adjusted to meet the required aspect. See `.set_adjustable` for further details. @@ -1298,6 +1291,10 @@ def set_aspect(self, aspect, adjustable=None, anchor=None): See `.set_anchor` for further details. + share : bool, optional + If ``True``, apply the settings to all shared Axes. + Default is ``False``. + See Also -------- matplotlib.axes.Axes.set_adjustable @@ -1306,54 +1303,73 @@ def set_aspect(self, aspect, adjustable=None, anchor=None): matplotlib.axes.Axes.set_anchor defining the position in case of extra space. """ - if (isinstance(aspect, six.string_types) + if not (isinstance(aspect, six.string_types) and aspect in ('equal', 'auto')): - self._aspect = aspect + aspect = float(aspect) # raise ValueError if necessary + if share: + axes = set(self._shared_x_axes.get_siblings(self) + + self._shared_y_axes.get_siblings(self)) else: - self._aspect = float(aspect) # raise ValueError if necessary + axes = [self] + for ax in axes: + ax._aspect = aspect + + if adjustable is None: + adjustable = self._adjustable + self.set_adjustable(adjustable, share=share) # Handle sharing. - if adjustable is not None: - self.set_adjustable(adjustable) if anchor is not None: - self.set_anchor(anchor) + self.set_anchor(anchor, share=share) self.stale = True def get_adjustable(self): return self._adjustable - def set_adjustable(self, adjustable): + def set_adjustable(self, adjustable, share=False): """ Define which parameter the Axes will change to achieve a given aspect. - Possible values are: - - ============ ===================================== - value description - ============ ===================================== - 'box' change the physical size of the Axes - 'datalim' change xlim or ylim - 'box-forced' same as 'box', but axes can be shared - ============ ===================================== + Parameters + ---------- + adjustable : ['box' | 'datalim'] + If 'box', change the physical dimensions of the Axes. + If 'datalim', change the ``x`` or ``y`` data limits. - 'box' does not allow axes sharing, as this can cause - unintended side effect. For cases when sharing axes is - fine, use 'box-forced'. + share : bool, optional + If ``True``, apply the settings to all shared Axes. + Default is ``False``. - .. ACCEPTS: [ 'box' | 'datalim' | 'box-forced' ] + .. ACCEPTS: [ 'box' | 'datalim'] See Also -------- matplotlib.axes.Axes.set_aspect for a description of aspect handling. - """ - 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 + + Notes + ----- + Shared Axes (of which twinned Axes are a special case) + impose restrictions on how aspect ratios can be imposed. + For twinned Axes, use 'datalim'. For Axes that share both + x and y, use 'box'. Otherwise, either 'datalim' or 'box' + may be used. These limitations are partly a requirement + to avoid over-specification, and partly a result of the + particular implementation we are currently using, in + which the adjustments for aspect ratios are done sequentially + and independently on each Axes as it is drawn. + """ + if adjustable == 'box-forced': + warnings.warn("The 'box-forced' keyword argument is deprecated" + " since 2.2.", cbook.mplDeprecation) + if adjustable not in ('box', 'datalim', 'box-forced'): + raise ValueError("argument must be 'box', or 'datalim'") + if share: + axes = set(self._shared_x_axes.get_siblings(self) + + self._shared_y_axes.get_siblings(self)) else: - raise ValueError('argument must be "box", or "datalim"') + axes = [self] + for ax in axes: + ax._adjustable = adjustable self.stale = True def get_anchor(self): @@ -1369,7 +1385,7 @@ def get_anchor(self): """ return self._anchor - def set_anchor(self, anchor): + def set_anchor(self, anchor, share=False): """ Define the anchor location. @@ -1405,16 +1421,26 @@ def set_anchor(self, anchor): | 'SW' | 'S' | 'SE' | +------+------+------+ + share : bool, optional + If ``True``, apply the settings to all shared Axes. + Default is ``False``. + See Also -------- matplotlib.axes.Axes.set_aspect for a description of aspect handling. """ - if anchor in mtransforms.Bbox.coefs or len(anchor) == 2: - self._anchor = anchor - else: + if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2): raise ValueError('argument must be among %s' % ', '.join(mtransforms.Bbox.coefs)) + if share: + axes = set(self._shared_x_axes.get_siblings(self) + + self._shared_y_axes.get_siblings(self)) + else: + axes = [self] + for ax in axes: + ax._anchor = anchor + self.stale = True def get_data_ratio(self): @@ -1447,21 +1473,22 @@ def get_data_ratio_log(self): def apply_aspect(self, position=None): """ - Adjust the Axes so that it fulfills its aspect setting. + Adjust the Axes for a specified data aspect ratio. - Depending on `.get_adjustable` and `.get_anchor` this will either - modify the Axes box or the view limits. + Depending on `.get_adjustable` this will modify either the Axes box + (position) or the view limits. In the former case, `.get_anchor` + will affect the position. Notes ----- - This is automatically called on draw. So you won't need to call this - yourself in most cases. One exception may be if you need to update the - Axes before drawing. + This is called automatically when each Axes is drawn. You may need + to call it yourself if you need to update the Axes position and/or + view limits before the Figure is drawn. See Also -------- matplotlib.axes.Axes.set_aspect - for a description of aspect handling. + for a description of aspect ratio handling. matplotlib.axes.Axes.set_adjustable defining the parameter to adjust in order to meet the required aspect. @@ -1500,17 +1527,12 @@ 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']: + if self in self._twinned_axes: + raise RuntimeError("Adjustable 'box' is not allowed in a" + " twinned Axes. Use 'datalim' instead.") if aspect_scale_mode == "log": box_aspect = A * self.get_data_ratio_log() else: @@ -1565,15 +1587,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: @@ -1581,7 +1603,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 @@ -4118,9 +4141,17 @@ def get_tightbbox(self, renderer, call_axes_locator=True): def _make_twin_axes(self, *kl, **kwargs): """ - make a twinx axes of self. This is used for twinx and twiny. + Make a twinx axes of self. This is used for twinx and twiny. """ + # Typically, SubplotBase._make_twin_axes is called instead of this. + # There is also an override in axes_grid1/axes_divider.py. + if 'sharex' in kwargs and 'sharey' in kwargs: + raise ValueError("Twinned Axes may share only one axis.") 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') + self._twinned_axes.join(self, ax2) return ax2 def twinx(self): @@ -4183,9 +4214,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/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index 10979bfd2c2f..9c033c728889 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -144,9 +144,11 @@ def label_outer(self): def _make_twin_axes(self, *kl, **kwargs): """ - make a twinx axes of self. This is used for twinx and twiny. + Make a twinx axes of self. This is used for twinx and twiny. """ from matplotlib.projections import process_projection_requirements + if 'sharex' in kwargs and 'sharey' in kwargs: + raise ValueError("Twinned Axes may share only one axis.") kl = (self.get_subplotspec(),) + kl projection_class, kwargs, key = process_projection_requirements( self.figure, *kl, **kwargs) @@ -154,6 +156,9 @@ def _make_twin_axes(self, *kl, **kwargs): ax2 = subplot_class_factory(projection_class)(self.figure, *kl, **kwargs) self.figure.add_subplot(ax2) + self.set_adjustable('datalim') + ax2.set_adjustable('datalim') + self._twinned_axes.join(self, ax2) return ax2 _subplot_classes = {} 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 a47486369916..16078e93c14e 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4439,6 +4439,61 @@ def test_empty_shared_subplots(): assert y1 >= 6 +def test_shared_with_aspect_1(): + # 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' + + +def test_shared_with_aspect_2(): + # 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() + + +def test_shared_with_aspect_3(): + # 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 d36f806e63f3..628506f4db48 100644 --- a/lib/matplotlib/tests/test_skew.py +++ b/lib/matplotlib/tests/test_skew.py @@ -190,14 +190,14 @@ def test_set_line_coll_dash_image(): @image_comparison(baseline_images=['skew_rects'], remove_text=True) 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 @@ -208,4 +208,4 @@ def test_skew_rectangle(): 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) diff --git a/lib/mpl_toolkits/axes_grid1/axes_divider.py b/lib/mpl_toolkits/axes_grid1/axes_divider.py index c86b2edd4043..b238e73cc5ec 100644 --- a/lib/mpl_toolkits/axes_grid1/axes_divider.py +++ b/lib/mpl_toolkits/axes_grid1/axes_divider.py @@ -915,9 +915,14 @@ def _make_twin_axes(self, *kl, **kwargs): Need to overload so that twinx/twiny will work with these axes. """ + if 'sharex' in kwargs and 'sharey' in kwargs: + raise ValueError("Twinned Axes may share only one axis.") ax2 = type(self)(self.figure, self.get_position(True), *kl, **kwargs) ax2.set_axes_locator(self.get_axes_locator()) self.figure.add_axes(ax2) + self.set_adjustable('datalim') + ax2.set_adjustable('datalim') + self._twinned_axes.join(self, ax2) return ax2 _locatableaxes_classes = {}