8000 Reuse InsetLocator to make twinned axes follow their parents. by anntzer · Pull Request #18456 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Reuse InsetLocator to make twinned axes follow their parents. #18456

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,14 @@
import matplotlib.tri as mtri
import matplotlib.units as munits
from matplotlib import _preprocess_data, rcParams
from matplotlib.axes._base import _AxesBase, _process_plot_format
from matplotlib.axes._base import (
_AxesBase, _TransformedBoundsLocator, _process_plot_format)
from matplotlib.axes._secondary_axes import SecondaryAxis
from matplotlib.container import BarContainer, ErrorbarContainer, StemContainer

_log = logging.getLogger(__name__)


class _InsetLocator:
"""
Axes locator for `.Axes.inset_axes`.

The locater is a callable object used in `.Axes.set_aspect` to compute the
axes location depending on the renderer.
"""

def __init__(self, bounds, transform):
"""
*bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together
specify the position of the inset axes.
"""
self._bounds = bounds
self._transform = transform

def __call__(self, ax, renderer):
# Subtracting transFigure will typically rely on inverted(), freezing
# the transform; thus, this needs to be delayed until draw time as
# transFigure may otherwise change after this is evaluated.
return mtransforms.TransformedBbox(
mtransforms.Bbox.from_bounds(*self._bounds),
self._transform - ax.figure.transFigure)


# The axes module contains all the wrappers to plotting functions.
# All the other methods should go in the _AxesBase class.

Expand Down Expand Up @@ -365,7 +341,7 @@ def inset_axes(self, bounds, *, transform=None, zorder=5, **kwargs):
kwargs.setdefault('label', 'inset_axes')

# This puts the rectangle into figure-relative coordinates.
inset_locator = _InsetLocator(bounds, transform)
inset_locator = _TransformedBoundsLocator(bounds, transform)
bounds = inset_locator(self, None).bounds
inset_ax = Axes(self.figure, bounds, zorder=zorder, **kwargs)
# this locator lets the axes move if in data coordinates.
Expand Down
30 changes: 29 additions & 1 deletion lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,31 @@ def wrapper(self, *args, **kwargs):
setattr(owner, name, wrapper)


class _TransformedBoundsLocator:
"""
Axes locator for `.Axes.inset_axes` and similarly positioned axes.

The locator is a callable object used in `.Axes.set_aspect` to compute the
axes location depending on the renderer.
"""

def __init__(self, bounds, transform):
"""
*bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together
specify the position of the inset axes.
"""
self._bounds = bounds
self._transform = transform

def __call__(self, ax, renderer):
# Subtracting transFigure will typically rely on inverted(), freezing
# the transform; thus, this needs to be delayed until draw time as
# transFigure may otherwise change after this is evaluated.
return mtransforms.TransformedBbox(
mtransforms.Bbox.from_bounds(*self._bounds),
self._transform - ax.figure.transFigure)


def _process_plot_format(fmt):
"""
Convert a MATLAB style color/line style format string to a (*linestyle*,
Expand Down Expand Up @@ -4336,7 +4361,10 @@ def _make_twin_axes(self, *args, **kwargs):
# Typically, SubplotBase._make_twin_axes is called instead of this.
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), *args, **kwargs)
ax2 = self.figure.add_axes(
self.get_position(True), *args, **kwargs,
axes_locator=_TransformedBoundsLocator(
[0, 0, 1, 1], self.transAxes))
self.set_adjustable('datalim')
ax2.set_adjustable('datalim')
self._twinned_axes.join(self, ax2)
Expand Down
10 changes: 4 additions & 6 deletions lib/matplotlib/axes/_secondary_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
import matplotlib.cbook as cbook
import matplotlib.docstring as docstring
import matplotlib.ticker as mticker
from matplotlib.axes import _axes
from matplotlib.axes._base import _AxesBase
from matplotlib.axes._base import _AxesBase, _TransformedBoundsLocator


class SecondaryAxis(_AxesBase):
Expand Down Expand Up @@ -114,13 +113,12 @@ def set_location(self, location):
else:
bounds = [self._pos, 0, 1e-10, 1]

secondary_locator = _axes._InsetLocator(bounds, self._parent.transAxes)

# this locator lets the axes move in the parent axes coordinates.
# so it never needs to know where the parent is explicitly in
# figure coordinates.
# it gets called in `ax.apply_aspect() (of all places)
self.set_axes_locator(secondary_locator)
# it gets called in ax.apply_aspect() (of all places)
self.set_axes_locator(
_TransformedBoundsLocator(bounds, self._parent.transAxes))

def apply_aspect(self, position=None):
# docstring inherited.
Expand Down
10 changes: 1 addition & 9 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2424,15 +2424,7 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None,
"constrained_layout==False. ")
self.subplotpars.update(left, bottom, right, top, wspace, hspace)
for ax in self.axes:
if not isinstance(ax, SubplotBase):
# Check if sharing a subplots axis
if isinstance(ax._sharex, SubplotBase):
ax._sharex.update_params()
ax.set_position(ax._sharex.figbox)
elif isinstance(ax._sharey, SubplotBase):
ax._sharey.update_params()
ax.set_position(ax._sharey.figbox)
else:
if isinstance(ax, SubplotBase):
ax.update_params()
ax.set_position(ax.figbox)
self.stale = True
Expand Down
13 changes: 1 addition & 12 deletions lib/matplotlib/gridspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,18 +486,7 @@ def update(self, **kwargs):
raise AttributeError(f"{k} is an unknown keyword")
for figmanager in _pylab_helpers.Gcf.figs.values():
for ax in figmanager.canvas.figure.axes:
# copied from Figure.subplots_adjust
if not isinstance(ax, mpl.axes.SubplotBase):
# Check if sharing a subplots axis
if isinstance(ax._sharex, mpl.axes.SubplotBase):
if ax._sharex.get_subplotspec().get_gridspec() == self:
ax._sharex.update_params()
ax._set_position(ax._sharex.figbox)
elif isinstance(ax._sharey, mpl.axes.SubplotBase):
if ax._sharey.get_subplotspec().get_gridspec() == self:
ax._sharey.update_params()
ax._set_position(ax._sharey.figbox)
else:
if isinstance(ax, mpl.axes.SubplotBase):
ss = ax.get_subplotspec().get_topmost_subplotspec()
if ss.get_gridspec() == self:
ax.update_params()
Expand Down
9 changes: 9 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6513,3 +6513,12 @@ def test_multiplot_autoscale():
ax2.axhspan(-5, 5)
xlim = ax1.get_xlim()
assert np.allclose(xlim, [0.5, 4.5])


def test_sharing_does_not_link_positions():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

858C

does not or does? I'm confused, as the final assert checks for equality.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's checking that the position of the shared axes did not change (i.e. that it did not get linked to the position of the sharee).

fig = plt.figure()
ax0 = fig.add_subplot(221)
ax1 = fig.add_axes([.6, .6, .3, .3], sharex=ax0)
init_pos = ax1.get_position()
fig.subplots_adjust(left=0)
assert (ax1.get_position().get_points() == init_pos.get_points()).all()
0