Skip to content

Commit b57a4da

Browse files
committed
Allow sharex/y after axes creation.
This intentionally does not allow unsharing or changing the shared axes, as there are bigger questions on the API there. The API is named `Axes.sharex()` to allow for a later `Axes.unsharex()`, though (`set_sharex()` would be fine, but `set_unsharex()`? or perhaps `set_sharex(None)`, though). An example use case is creating a grid with `subplots()`, but with custom sharing relationships between the subplots -- e.g., sharex/sharey across all, except the first row of axes which only shares x with their column and the first column which only shares y with their lines.
1 parent af4ab53 commit b57a4da

File tree

4 files changed

+80
-31
lines changed

4 files changed

+80
-31
lines changed

doc/api/axes_api.rst

+5-2
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,8 @@ Adding Artists
447447
Axes.add_table
448448

449449

450-
Twinning
451-
========
450+
Twinning and sharing
451+
====================
452452

453453
.. autosummary::
454454
:toctree: _as_gen
@@ -458,6 +458,9 @@ Twinning
458458
Axes.twinx
459459
Axes.twiny
460460

461+
Axes.sharex
462+
Axes.sharey
463+
461464
Axes.get_shared_x_axes
462465
Axes.get_shared_y_axes
463466

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
`.Axes.sharex`, `.Axes.sharey`
2+
------------------------------
3+
These new methods allow sharing axes *immediately* after creating them. For
4+
example, they can be used to selectively link some axes created all together
5+
using `~.Figure.subplots`.
6+
7+
Note that they may *not* be used to share axes after any operation (e.g.,
8+
drawing) has occurred on them.

examples/subplots_axes_and_figures/subplots_demo.py

+17
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,23 @@
176176
for ax in axs.flat:
177177
ax.label_outer()
178178

179+
###############################################################################
180+
# If you want a more complex sharing structure, you can first create the
181+
# grid of axes with no sharing, and then call `.axes.Axes.sharex` or
182+
# `.axes.Axes.sharey` to add sharing info a posteriori.
183+
184+
fig, axs = plt.subplots(2, 2)
185+
axs[0, 0].plot(x, y)
186+
axs[0, 0].set_title("main")
187+
axs[1, 0].plot(x, y**2)
188+
axs[1, 0].set_title("shares x with main")
189+
axs[1, 0].sharex(axs[0, 0])
190+
axs[0, 1].plot(x + 1, y + 1)
191+
axs[0, 1].set_title("unrelated")
192+
axs[1, 1].plot(x + 2, y + 2)
193+
axs[1, 1].set_title("also unrelated")
194+
fig.tight_layout()
195+
179196
###############################################################################
180197
# Polar axes
181198
# """"""""""

lib/matplotlib/axes/_base.py

+50-29
Original file line numberDiff line numberDiff line change
@@ -432,16 +432,8 @@ def __init__(self, fig, rect,
432432
self._anchor = 'C'
433433
self._stale_viewlim_x = False
434434
self._stale_viewlim_y = False
435-
self._sharex = sharex
436-
self._sharey = sharey
437-
if sharex is not None:
438-
if not isinstance(sharex, _AxesBase):
439-
raise TypeError('sharex must be an axes, not a bool')
440-
self._shared_x_axes.join(self, sharex)
441-
if sharey is not None:
442-
if not isinstance(sharey, _AxesBase):
443-
raise TypeError('sharey must be an axes, not a bool')
444-
self._shared_y_axes.join(self, sharey)
435+
self._sharex = None
436+
self._sharey = None
445437
self.set_label(label)
446438
self.set_figure(fig)
447439
self.set_box_aspect(box_aspect)
@@ -460,6 +452,11 @@ def __init__(self, fig, rect,
460452
self._rasterization_zorder = None
461453
self.cla()
462454

455+
if sharex is not None:
456+
self.sharex(sharex)
457+
if sharey is not None:
458+
self.sharey(sharey)
459+
463460
# funcs used to format x and y - fall back on major formatters
464461
self.fmt_xdata = None
465462
self.fmt_ydata = None
@@ -952,6 +949,44 @@ def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'):
952949
return OrderedDict((side, mspines.Spine.linear_spine(self, side))
953950
for side in ['left', 'right', 'bottom', 'top'])
954951

952+
def sharex(self, other):
953+
"""
954+
Share the x-axis with *other*.
955+
956+
This is equivalent to passing ``sharex=other`` when constructing the
957+
axes, and cannot be used if the x-axis is already being shared with
958+
another axes.
959+
"""
960+
cbook._check_isinstance(_AxesBase, other=other)
961+
if self._sharex is not None and other is not self._sharex:
962+
raise ValueError("x-axis is already shared")
963+
self._shared_x_axes.join(self, other)
964+
self._sharex = other
965+
self.xaxis.major = other.xaxis.major # Ticker instances holding
966+
self.xaxis.minor = other.xaxis.minor # locator and formatter.
967+
x0, x1 = other.get_xlim()
968+
self.set_xlim(x0, x1, emit=False, auto=other.get_autoscalex_on())
969+
self.xaxis._scale = other.xaxis._scale
970+
971+
def sharey(self, other):
972+
"""
973+
Share the y-axis with *other*.
974+
975+
This is equivalent to passing ``sharey=other`` when constructing the
976+
axes, and cannot be used if the y-axis is already being shared with
977+
another axes.
978+
"""
979+
cbook._check_isinstance(_AxesBase, other=other)
980+
if self._sharey is not None and other is not self._sharey:
981+
raise ValueError("y-axis is already shared")
982+
self._shared_y_axes.join(self, other)
983+
self._sharey = other
984+
self.yaxis.major = other.yaxis.major # Ticker instances holding
985+
self.yaxis.minor = other.yaxis.minor # locator and formatter.
986+
y0, y1 = other.get_ylim()
987+
self.set_ylim(y0, y1, emit=False, auto=other.get_autoscaley_on())
988+
self.yaxis._scale = other.yaxis._scale
989+
955990
def cla(self):
956991
"""Clear the current axes."""
957992
# Note: this is called by Axes.__init__()
@@ -975,38 +1010,25 @@ def cla(self):
9751010
self.callbacks = cbook.CallbackRegistry()
9761011

9771012
if self._sharex is not None:
978-
# major and minor are axis.Ticker class instances with
979-
# locator and formatter attributes
980-
self.xaxis.major = self._sharex.xaxis.major
981-
self.xaxis.minor = self._sharex.xaxis.minor
982-
x0, x1 = self._sharex.get_xlim()
983-
self.set_xlim(x0, x1, emit=False,
984-
auto=self._sharex.get_autoscalex_on())
985-
self.xaxis._scale = self._sharex.xaxis._scale
1013+
self.sharex(self._sharex)
9861014
else:
9871015
self.xaxis._set_scale('linear')
9881016
try:
9891017
self.set_xlim(0, 1)
9901018
except TypeError:
9911019
pass
992-
9931020
if self._sharey is not None:
994-
self.yaxis.major = self._sharey.yaxis.major
995-
self.yaxis.minor = self._sharey.yaxis.minor
996-
y0, y1 = self._sharey.get_ylim()
997-
self.set_ylim(y0, y1, emit=False,
998-
auto=self._sharey.get_autoscaley_on())
999-
self.yaxis._scale = self._sharey.yaxis._scale
1021+
self.sharey(self._sharey)
10001022
else:
10011023
self.yaxis._set_scale('linear')
10021024
try:
10031025
self.set_ylim(0, 1)
10041026
except TypeError:
10051027
pass
1028+
10061029
# update the minor locator for x and y axis based on rcParams
10071030
if rcParams['xtick.minor.visible']:
10081031
self.xaxis.set_minor_locator(mticker.AutoMinorLocator())
1009-
10101032
if rcParams['ytick.minor.visible']:
10111033
self.yaxis.set_minor_locator(mticker.AutoMinorLocator())
10121034

@@ -1088,11 +1110,10 @@ def cla(self):
10881110

10891111
self._shared_x_axes.clean()
10901112
self._shared_y_axes.clean()
1091-
if self._sharex:
1113+
if self._sharex is not None:
10921114
self.xaxis.set_visible(xaxis_visible)
10931115
self.patch.set_visible(patch_visible)
1094-
1095-
if self._sharey:
1116+
if self._sharey is not None:
10961117
self.yaxis.set_visible(yaxis_visible)
10971118
self.patch.set_visible(patch_visible)
10981119

0 commit comments

Comments
 (0)