FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

ENH: accept 'all'/'row'/'col' axis sharing in subplot_mosaic by aimldlds · Pull Request #32239 · matplotlib/matplotlib · GitHub

Repository navigation

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .py  (3) .pyi  (1) .rst  (1) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
``subplot_mosaic`` accepts the same axis-sharing spellings as ``subplots``
--------------------------------------------------------------------------
The *sharex* and *sharey* parameters of `.Figure.subplot_mosaic` and
`.pyplot.subplot_mosaic` now accept the strings ``'none'``, ``'all'``,
``'row'`` and ``'col'`` in addition to the booleans, matching
`.Figure.subplots`.

.. plot::
:include-source: true

import matplotlib.pyplot as plt

fig, ax_dict = plt.subplot_mosaic("AB;CD", sharex="col", sharey="row")

Because an Axes of a mosaic may span several rows or columns, it belongs to
every row or column it covers and therefore joins the sharing groups of all of
them. For instance in ``'AAE;C.E'`` the Axes *E* spans both rows, so
``sharex='row'`` shares the x-axis among *A*, *C* and *E* alike. The Axes of a
nested mosaic are attributed to the cell of the outer mosaic holding them.
102 changes: 90 additions & 12 deletions lib/matplotlib/figure.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2034,11 +2034,28 @@ def subplot_mosaic(self, mosaic, *, sharex=False, sharey=False,
The Axes identifiers may be `str` or a non-iterable hashable
object (e.g. `tuple` s may not be used).

sharex, sharey : bool, default: False
If True, the x-axis (*sharex*) or y-axis (*sharey*) will be shared
among all subplots. In that case, tick label visibility and axis
units behave as for `subplots`. If False, each subplot's x- or
y-axis will be independent.
sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False
Controls sharing of properties among the x-axis (*sharex*) or
y-axis (*sharey*):

- True or 'all': x- or y-axis will be shared among all subplots.
- False or 'none': each subplot x- or y-axis will be independent.
- 'row': each subplot row will share an x- or y-axis.
- 'col': each subplot column will share an x- or y-axis.

In the shared cases, tick label visibility and axis units behave
as for `subplots`.

Because an Axes of a mosaic may span several rows or columns, it
belongs to every row or column it covers and therefore joins the
groups of all of them. For instance in ``'AAE;C.E'`` the Axes
*E* spans both rows, so ``sharex='row'`` shares the x-axis among
*A*, *C* and *E* alike. The Axes of a nested mosaic are
attributed to the cell of the outer mosaic holding them.

.. versionchanged:: 3.12
The 'none', 'all', 'row' and 'col' spellings were added; before
only the booleans were accepted.

width_ratios : array-like of length *ncols*, optional
Defines the relative widths of the columns. Each column gets a
Expand Down Expand Up @@ -2117,8 +2134,16 @@ def subplot_mosaic(self, mosaic, *, sharex=False, sharey=False,

per_subplot_kw = self._norm_per_subplot_kw(per_subplot_kw)

# Only accept strict bools to allow a possible future API expansion.
_api.check_isinstance(bool, sharex=sharex, sharey=sharey)
# Normalize the bool spellings to the string spellings used by
# `.Figure.subplots`, so that the two APIs accept the same values.
if not isinstance(sharex, str):
_api.check_isinstance(bool, sharex=sharex)
sharex = "all" if sharex else "none"
if not isinstance(sharey, str):
_api.check_isinstance(bool, sharey=sharey)
sharey = "all" if sharey else "none"
_api.check_in_list(["all", "row", "col", "none"],
sharex=sharex, sharey=sharey)

def _make_array(inp):
"""
Expand Down Expand Up @@ -2277,13 +2302,66 @@ def _do_layout(gs, mosaic, unique_ids, nested):
rows, cols = mosaic.shape
gs = self.add_gridspec(rows, cols, **gridspec_kw)
ret = _do_layout(gs, mosaic, *_identify_keys_and_nested(mosaic))
ax0 = next(iter(ret.values()))
for ax in ret.values():
if sharex:
def _share_groups(mode, along_rows):
"""
Group the Axes of the mosaic that should share an axis.

Parameters
----------
mode : {'all', 'row', 'col', 'none'}
The sharing mode requested by the user.
along_rows : bool
If True group by shared *rows*, otherwise by shared *columns*.

Returns
-------
list of list of Axes
Each inner list is one group of Axes to be shared together;
the first entry is the Axes the others are shared with. The
groups are empty if *mode* is ``'none'``.

Notes
-----
An Axes that spans several rows (columns) belongs to every row
(column) it covers, so it transitively merges the groups of all
of those rows (columns). For example, in ``'AAE;C.E'`` the Axes
*E* spans both rows, so ``sharex='row'`` places *A*, *C* and *E*
in a single group. Axes of a nested mosaic are attributed to the
cell of the *outer* mosaic that contains them.
"""
if mode == 'none':
return []
axs = list(ret.values())
if mode == 'all':
return [axs]
# Grouper is a union-find; joining each Axes first keeps the
# mosaic order and makes sure un-grouped Axes are still present.
grouper = cbook.Grouper()
by_index = {}
for ax in axs:
grouper.join(ax)
ss = ax.get_subplotspec().get_topmost_subplotspec()
for i in (ss.rowspan if along_rows else ss.colspan):
by_index.setdefault(i, []).append(ax)
for group in by_index.values():
grouper.join(*group)
return list(grouper)

for group in _share_groups(sharex, along_rows=(sharex == 'row')):
ax0, *rest = group
for ax in rest:
ax.sharex(ax0)
ax._label_outer_xaxis(skip_non_rectangular_axes=True)
if sharey:
for group in _share_groups(sharey, along_rows=(sharey == 'row')):
ax0, *rest = group
for ax in rest:
ax.sharey(ax0)
# Match `.Figure.subplots`: only hide the inner tick labels when the
# sharing actually lines the Axes up along that direction.
if sharex in ['col', 'all']:
for ax in ret.values():
ax._label_outer_xaxis(skip_non_rectangular_axes=True)
if sharey in ['row', 'all']:
for ax in ret.values():
ax._label_outer_yaxis(skip_non_rectangular_axes=True)
if extra := set(per_subplot_kw) - set(ret):
raise ValueError(
Expand Down
12 changes: 6 additions & 6 deletions lib/matplotlib/figure.pyi
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,8 @@ class FigureBase(Artist):
self,
mosaic: str,
*,
sharex: bool = ...,
sharey: bool = ...,
sharex: bool | Literal["none", "all", "row", "col"] = ...,
sharey: bool | Literal["none", "all", "row", "col"] = ...,
width_ratios: ArrayLike | None = ...,
height_ratios: ArrayLike | None = ...,
empty_sentinel: str = ...,
Expand All @@ -269,8 +269,8 @@ class FigureBase(Artist):
self,
mosaic: list[HashableList[T]],
*,
sharex: bool = ...,
sharey: bool = ...,
sharex: bool | Literal["none", "all", "row", "col"] = ...,
sharey: bool | Literal["none", "all", "row", "col"] = ...,
width_ratios: ArrayLike | None = ...,
height_ratios: ArrayLike | None = ...,
empty_sentinel: T = ...,
Expand All @@ -283,8 +283,8 @@ class FigureBase(Artist):
self,
mosaic: list[HashableList[Hashable]],
*,
sharex: bool = ...,
sharey: bool = ...,
sharex: bool | Literal["none", "all", "row", "col"] = ...,
sharey: bool | Literal["none", "all", "row", "col"] = ...,
width_ratios: ArrayLike | None = ...,
height_ratios: ArrayLike | None = ...,
empty_sentinel: Any = ...,
Expand Down
43 changes: 30 additions & 13 deletions lib/matplotlib/pyplot.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1896,8 +1896,8 @@ def subplots(
def subplot_mosaic(
mosaic: str,
*,
sharex: bool = ...,
sharey: bool = ...,
sharex: bool | Literal["none", "all", "row", "col"] = ...,
sharey: bool | Literal["none", "all", "row", "col"] = ...,
width_ratios: ArrayLike | None = ...,
height_ratios: ArrayLike | None = ...,
empty_sentinel: str = ...,
Expand All @@ -1912,8 +1912,8 @@ def subplot_mosaic(
def subplot_mosaic[T](
mosaic: list[HashableList[T]],
*,
sharex: bool = ...,
sharey: bool = ...,
sharex: bool | Literal["none", "all", "row", "col"] = ...,
sharey: bool | Literal["none", "all", "row", "col"] = ...,
width_ratios: ArrayLike | None = ...,
height_ratios: ArrayLike | None = ...,
empty_sentinel: T = ...,
Expand All @@ -1928,8 +1928,8 @@ def subplot_mosaic[T](
def subplot_mosaic(
mosaic: list[HashableList[Hashable]],
*,
sharex: bool = ...,
sharey: bool = ...,
sharex: bool | Literal["none", "all", "row", "col"] = ...,
sharey: bool | Literal["none", "all", "row", "col"] = ...,
width_ratios: ArrayLike | None = ...,
height_ratios: ArrayLike | None = ...,
empty_sentinel: Any = ...,
Expand All @@ -1943,8 +1943,8 @@ def subplot_mosaic(
def subplot_mosaic[T](
mosaic: str | list[HashableList[T]] | list[HashableList[Hashable]],
*,
sharex: bool = False,
sharey: bool = False,
sharex: bool | Literal["none", "all", "row", "col"] = False,
sharey: bool | Literal["none", "all", "row", "col"] = False,
width_ratios: ArrayLike | None = None,
height_ratios: ArrayLike | None = None,
empty_sentinel: Any = '.',
Expand Down Expand Up @@ -1996,11 +1996,28 @@ def subplot_mosaic[T](
This only allows only single character Axes labels and does
not allow nesting but is very terse.

sharex, sharey : bool, default: False
If True, the x-axis (*sharex*) or y-axis (*sharey*) will be shared
among all subplots. In that case, tick label visibility and axis units
behave as for `subplots`. If False, each subplot's x- or y-axis will
be independent.
sharex, sharey : bool or {'none', 'all', 'row', 'col'}, default: False
Controls sharing of properties among the x-axis (*sharex*) or y-axis
(*sharey*):

- True or 'all': x- or y-axis will be shared among all subplots.
- False or 'none': each subplot x- or y-axis will be independent.
- 'row': each subplot row will share an x- or y-axis.
- 'col': each subplot column will share an x- or y-axis.

In the shared cases, tick label visibility and axis units behave as
for `subplots`.

Because an Axes of a mosaic may span several rows or columns, it
belongs to every row or column it covers and therefore joins the
groups of all of them. For instance in ``'AAE;C.E'`` the Axes *E*
spans both rows, so ``sharex='row'`` shares the x-axis among *A*,
*C* and *E* alike. The Axes of a nested mosaic are attributed to
the cell of the outer mosaic holding them.

.. versionchanged:: 3.12
The 'none', 'all', 'row' and 'col' spellings were added; before
only the booleans were accepted.

width_ratios : array-like of length *ncols*, optional
Defines the relative widths of the columns. Each column gets a
Expand Down
57 changes: 57 additions & 0 deletions lib/matplotlib/tests/test_figure.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1334,6 +1334,63 @@ def test_share_all(self):
assert all(ax.get_xscale() == "log" and ax.get_yscale() == "logit"
for ax in ax_dict.values())

@staticmethod
def _share_groups(ax_dict, which):
"""Return the sharing groups of *ax_dict* as sorted lists of labels."""
groups = {
frozenset(id(sib) for sib in
getattr(ax, f"get_shared_{which}_axes")()
.get_siblings(ax))
for ax in ax_dict.values()
}
return sorted(
sorted(label for label, ax in ax_dict.items() if id(ax) in group)
for group in groups
)

@pytest.mark.parametrize("share, expected", [
("none", [["A"], ["B"], ["C"], ["D"]]),
("all", [["A", "B", "C", "D"]]),
("row", [["A", "B"], ["C", "D"]]),
("col", [["A", "C"], ["B", "D"]]),
])
def test_share_string_grid(self, share, expected):
# On a plain grid the mosaic spellings must agree with `subplots`.
fig = plt.figure()
ax_dict = fig.subplot_mosaic("AB;CD", sharex=share, sharey=share)
assert self._share_groups(ax_dict, "x") == expected
assert self._share_groups(ax_dict, "y") == expected

@pytest.mark.parametrize("bool_spelling, str_spelling",
[(True, "all"), (False, "none")])
def test_share_bool_matches_string(self, bool_spelling, str_spelling):
mosaic = "AAE;C.E"
from_bool = plt.figure().subplot_mosaic(mosaic, sharex=bool_spelling)
from_str = plt.figure().subplot_mosaic(mosaic, sharex=str_spelling)
assert (self._share_groups(from_bool, "x")
== self._share_groups(from_str, "x"))

def test_share_spanning_axes(self):
# 'E' spans both rows, so sharing by row transitively joins every
# Axes, while sharing by column leaves 'E' on its own.
by_row = plt.figure().subplot_mosaic("AAE;C.E", sharex="row")
assert self._share_groups(by_row, "x") == [["A", "C", "E"]]
by_col = plt.figure().subplot_mosaic("AAE;C.E", sharex="col")
assert self._share_groups(by_col, "x") == [["A", "C"], ["E"]]

def test_share_nested(self):
# Axes of a nested mosaic belong to the outer cell holding them.
ax_dict = plt.figure().subplot_mosaic([["A", [["B"], ["C"]]]],
sharex="col")
assert self._share_groups(ax_dict, "x") == [["A"], ["B", "C"]]

def test_share_invalid(self):
fig = plt.figure()
with pytest.raises(ValueError, match="not a valid value for sharex"):
fig.subplot_mosaic("AB", sharex="peculiar")
with pytest.raises(TypeError, match="must be an instance of bool"):
fig.subplot_mosaic("AB", sharey=1)


def test_reused_gridspec():
"""Test that these all use the same gridspec"""
Expand Down
Loading

Back | FazBrowse Home | New Git URL