Skip to content

Commit

Permalink
Merge pull request matplotlib#26454 from timhoffm/label_outer_check_r…
Browse files Browse the repository at this point in the history
…ectilinear

Rename an internal parameter of _label_outer_x/yaxis()
  • Loading branch information
ksunden authored and timhoffm committed Aug 6, 2023
2 parents 868004b + 88e1353 commit b764dcd
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 30 deletions.
26 changes: 26 additions & 0 deletions doc/users/next_whats_new/label_outer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Remove inner ticks in ``label_outer()``
---------------------------------------
Up to now, ``label_outer()`` has only removed the ticklabels. The ticks lines
were left visible. This is now configurable through a new parameter
``label_outer(remove_inner_ticks=True)``.


.. plot::
:include-source: true

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)

fig, axs = plt.subplots(2, 2, sharex=True, sharey=True,
gridspec_kw=dict(hspace=0, wspace=0))

axs[0, 0].plot(x, np.sin(x))
axs[0, 1].plot(x, np.cos(x))
axs[1, 0].plot(x, -np.cos(x))
axs[1, 1].plot(x, -np.sin(x))

for ax in axs.flat:
ax.grid(color='0.9')
ax.label_outer(remove_inner_ticks=True)
41 changes: 30 additions & 11 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4490,20 +4490,29 @@ def get_shared_y_axes(self):
"""Return an immutable view on the shared y-axes Grouper."""
return cbook.GrouperView(self._shared_axes["y"])

def label_outer(self):
def label_outer(self, remove_inner_ticks=False):
"""
Only show "outer" labels and tick labels.
x-labels are only kept for subplots on the last row (or first row, if
labels are on the top side); y-labels only for subplots on the first
column (or last column, if labels are on the right side).
Parameters
----------
remove_inner_ticks : bool, default: False
If True, remove the inner ticks as well (not only tick labels).
"""
self._label_outer_xaxis(check_patch=False)
self._label_outer_yaxis(check_patch=False)
self._label_outer_xaxis(skip_non_rectangular_axes=False,
remove_inner_ticks=remove_inner_ticks)
self._label_outer_yaxis(skip_non_rectangular_axes=False,
remove_inner_ticks=remove_inner_ticks)

def _label_outer_xaxis(self, *, check_patch):
def _label_outer_xaxis(self, *, skip_non_rectangular_axes,
remove_inner_ticks=False):
# see documentation in label_outer.
if check_patch and not isinstance(self.patch, mpl.patches.Rectangle):
if skip_non_rectangular_axes and not isinstance(self.patch,
mpl.patches.Rectangle):
return
ss = self.get_subplotspec()
if not ss:
Expand All @@ -4512,19 +4521,25 @@ def _label_outer_xaxis(self, *, check_patch):
if not ss.is_first_row(): # Remove top label/ticklabels/offsettext.
if label_position == "top":
self.set_xlabel("")
self.xaxis.set_tick_params(which="both", labeltop=False)
top_kw = {'top': False} if remove_inner_ticks else {}
self.xaxis.set_tick_params(
which="both", labeltop=False, **top_kw)
if self.xaxis.offsetText.get_position()[1] == 1:
self.xaxis.offsetText.set_visible(False)
if not ss.is_last_row(): # Remove bottom label/ticklabels/offsettext.
if label_position == "bottom":
self.set_xlabel("")
self.xaxis.set_tick_params(which="both", labelbottom=False)
bottom_kw = {'bottom': False} if remove_inner_ticks else {}
self.xaxis.set_tick_params(
which="both", labelbottom=False, **bottom_kw)
if self.xaxis.offsetText.get_position()[1] == 0:
self.xaxis.offsetText.set_visible(False)

def _label_outer_yaxis(self, *, check_patch):
def _label_outer_yaxis(self, *, skip_non_rectangular_axes,
remove_inner_ticks=False):
# see documentation in label_outer.
if check_patch and not isinstance(self.patch, mpl.patches.Rectangle):
if skip_non_rectangular_axes and not isinstance(self.patch,
mpl.patches.Rectangle):
return
ss = self.get_subplotspec()
if not ss:
Expand All @@ -4533,13 +4548,17 @@ def _label_outer_yaxis(self, *, check_patch):
if not ss.is_first_col(): # Remove left label/ticklabels/offsettext.
if label_position == "left":
self.set_ylabel("")
self.yaxis.set_tick_params(which="both", labelleft=False)
left_kw = {'left': False} if remove_inner_ticks else {}
self.yaxis.set_tick_params(
which="both", labelleft=False, **left_kw)
if self.yaxis.offsetText.get_position()[0] == 0:
self.yaxis.offsetText.set_visible(False)
if not ss.is_last_col(): # Remove right label/ticklabels/offsettext.
if label_position == "right":
self.set_ylabel("")
self.yaxis.set_tick_params(which="both", labelright=False)
right_kw = {'right': False} if remove_inner_ticks else {}
self.yaxis.set_tick_params(
which="both", labelright=False, **right_kw)
if self.yaxis.offsetText.get_position()[0] == 1:
self.yaxis.offsetText.set_visible(False)

Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/axes/_base.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ class _AxesBase(martist.Artist):
def twiny(self) -> _AxesBase: ...
def get_shared_x_axes(self) -> cbook.GrouperView: ...
def get_shared_y_axes(self) -> cbook.GrouperView: ...
def label_outer(self) -> None: ...
def label_outer(self, remove_inner_ticks: bool) -> None: ...

# The methods underneath this line are added via the `_axis_method_wrapper` class
# Initially they are set to an object, but that object uses `__set_name__` to override
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -2118,10 +2118,10 @@ def _do_layout(gs, mosaic, unique_ids, nested):
for ax in ret.values():
if sharex:
ax.sharex(ax0)
ax._label_outer_xaxis(check_patch=True)
ax._label_outer_xaxis(skip_non_rectangular_axes=True)
if sharey:
ax.sharey(ax0)
ax._label_outer_yaxis(check_patch=True)
ax._label_outer_yaxis(skip_non_rectangular_axes=True)
if extra := set(per_subplot_kw) - set(ret):
raise ValueError(
f"The keys {extra} are in *per_subplot_kw* "
Expand Down
4 changes: 2 additions & 2 deletions lib/matplotlib/gridspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,10 +301,10 @@ def subplots(self, *, sharex=False, sharey=False, squeeze=True,
# turn off redundant tick labeling
if sharex in ["col", "all"]:
for ax in axarr.flat:
ax._label_outer_xaxis(check_patch=True)
ax._label_outer_xaxis(skip_non_rectangular_axes=True)
if sharey in ["row", "all"]:
for ax in axarr.flat:
ax._label_outer_yaxis(check_patch=True)
ax._label_outer_yaxis(skip_non_rectangular_axes=True)

if squeeze:
# Discarding unneeded dimensions that equal 1. If we only have one
Expand Down
51 changes: 37 additions & 14 deletions lib/matplotlib/tests/test_subplots.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def check_shared(axs, x_shared, y_shared):
i1, i2, "not " if shared[i1, i2] else "", name)


def check_visible(axs, x_visible, y_visible):
def check_ticklabel_visible(axs, x_visible, y_visible):
"""Check that the x and y ticklabel visibility is as specified."""
for i, (ax, vx, vy) in enumerate(zip(axs, x_visible, y_visible)):
for l in ax.get_xticklabels() + [ax.xaxis.offsetText]:
assert l.get_visible() == vx, \
Expand All @@ -40,6 +41,20 @@ def check_visible(axs, x_visible, y_visible):
assert ax.get_ylabel() == ""


def check_tick1_visible(axs, x_visible, y_visible):
"""
Check that the x and y tick visibility is as specified.
Note: This only checks the tick1line, i.e. bottom / left ticks.
"""
for ax, visible, in zip(axs, x_visible):
for tick in ax.xaxis.get_major_ticks():
assert tick.tick1line.get_visible() == visible
for ax, y_visible, in zip(axs, y_visible):
for tick in ax.yaxis.get_major_ticks():
assert tick.tick1line.get_visible() == visible


def test_shared():
rdim = (4, 4, 2)
share = {
Expand Down Expand Up @@ -90,16 +105,24 @@ def test_shared():
f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, sharex=xo, sharey=yo)
axs = [a1, a2, a3, a4]
check_shared(axs, share[xo], share[yo])
check_visible(axs, visible['x'][xo], visible['y'][yo])
check_ticklabel_visible(axs, visible['x'][xo], visible['y'][yo])
plt.close(f)

# test label_outer
f, ((a1, a2), (a3, a4)) = plt.subplots(2, 2, sharex=True, sharey=True)
axs = [a1, a2, a3, a4]
for ax in axs:

@pytest.mark.parametrize('remove_ticks', [True, False])
def test_label_outer(remove_ticks):
f, axs = plt.subplots(2, 2, sharex=True, sharey=True)
for ax in axs.flat:
ax.set(xlabel="foo", ylabel="bar")
ax.label_outer()
check_visible(axs, [False, False, True, True], [True, False, True, False])
ax.label_outer(remove_inner_ticks=remove_ticks)
check_ticklabel_visible(
axs.flat, [False, False, True, True], [True, False, True, False])
if remove_ticks:
check_tick1_visible(
axs.flat, [False, False, True, True], [True, False, True, False])
else:
check_tick1_visible(
axs.flat, [True, True, True, True], [True, True, True, True])


def test_label_outer_span():
Expand All @@ -118,28 +141,28 @@ def test_label_outer_span():
a4 = fig.add_subplot(gs[2, 1])
for ax in fig.axes:
ax.label_outer()
check_visible(
check_ticklabel_visible(
fig.axes, [False, True, False, True], [True, True, False, False])


def test_label_outer_non_gridspec():
ax = plt.axes((0, 0, 1, 1))
ax.label_outer() # Does nothing.
check_visible([ax], [True], [True])
check_ticklabel_visible([ax], [True], [True])


def test_shared_and_moved():
# test if sharey is on, but then tick_left is called that labels don't
# re-appear. Seaborn does this just to be sure yaxis is on left...
f, (a1, a2) = plt.subplots(1, 2, sharey=True)
check_visible([a2], [True], [False])
check_ticklabel_visible([a2], [True], [False])
a2.yaxis.tick_left()
check_visible([a2], [True], [False])
check_ticklabel_visible([a2], [True], [False])

f, (a1, a2) = plt.subplots(2, 1, sharex=True)
check_visible([a1], [False], [True])
check_ticklabel_visible([a1], [False], [True])
a2.xaxis.tick_bottom()
check_visible([a1], [False], [True])
check_ticklabel_visible([a1], [False], [True])


def test_exceptions():
Expand Down

0 comments on commit b764dcd

Please sign in to comment.