Repository navigation
ENH: Added sharex/sharey string support to subplot_mosaic - #32437
nillohitroy wants to merge 5 commits into
Conversation
|
Thank you for opening your first PR into Matplotlib! If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks. We also ask that you please finish addressing any review comments on this PR and wait for it to be merged (or closed) before opening a new one, as it can be a valuable learning experience to go through the review process. You can also join us on discourse chat for real-time discussion. For details on testing, writing docs, and our review process, please see the developer guide. We strive to be a welcoming and open project. Please follow our Code of Conduct. |
|
Dear Maintainers, |
|
Assuming that I'm talking to an actual human: this PR is currently failing to pass the Linting and MyPy Stubtest checks. The AppVeyor build failure is somewhat expected as AppVeyor has been flaky recently. Generally, you will want to make sure that all checks pass on any PR that you open to a repository. You need to get at least Linting and the stub test to pass first. |
Thank you sir.. on it.. |
|
Hi @iccir |
|
I think a rebase should sort that out. Assuming you have set the upstream remote as described here, try this: Make a backup branch in case something else goes wrong Rebase on the upstream branch
|
|
Hey, it happens - git can be tricky :) It looks like you made two commits: I'd do something like the following: Then I'd follow @rcomer's instructions above. You can always reapply the patch with: |
4f25789 to
1416ea3
Compare
|
If you get a test failure that you don't understand, there are several possibilities:
More commonly, however, #3 can indicate that something is wrong with the Continuous Integration (CI) system. You can often look at other pull requests to see if they are having similar issues. Python 3.12 on macOS 15 has been failing all day - here's another example with the same error. So, it's probably not an error in your code! |
I also have the same view as you. It is showing that the test is failing only in macos-15 (macos26 is running successfully). Also, I have tested the code locally as well, so I'm pretty confident about it. My request is if you could review my code so as to ensure if there are other major issues or this code can be merged to the repo.. |
|
Hi @rcomer |
| bottom_edge = max(ax.get_subplotspec().rowspan.stop for ax in group) | ||
| for ax in group: | ||
| if ax.get_subplotspec().rowspan.stop < bottom_edge: | ||
| ax.tick_params(labelbottom=False) |
There was a problem hiding this comment.
@nillohitroy The copilot suggestion to use physical coordinates is based on the assumption that grid nesting should not play a role. Have we decided that this is the case? For
["A", [["B", "C"],
["B", "D"]],
]
should A and B sharey? Are there realistic cases where that is necessary? It would be simpler to claim that col/row sharing across nested grids is not supported. The physical calculation is a bit annoying because you need tolerances.
|
I'm sorry but I do not think I will be able to review this any time soon. |
I appreciate your efforts and I can understand it. I will figure it out in some way.. |
|
Hi @iccir |
| # ax.sharey(ax0) | ||
| # ax._label_outer_yaxis(skip_non_rectangular_axes=True) | ||
|
|
||
| # Symmetric Sharing |
There was a problem hiding this comment.
| # Symmetric Sharing |
That comment does not tell much. Please write a proper docstring.
| self, | ||
| mosaic: str, | ||
| *, | ||
| sharex: bool = ..., |
There was a problem hiding this comment.
I'm not clear whether we want "all" as synonym for True. On the one hand it's unnecessarily redundant, on the other hand it's consistent with subplots. I'm slightly inclined towards the narrower API. Others may have stronger opinions.
But if we support "all", we also must "none".
…bels and updated docstrings
|
@timhoffm |
|
Hi @timhoffm |
| # Testing row sharing for y-axis | ||
| axd["A"].set_ylim(0, 50) | ||
| axd["C"].set_ylim(-10, 10) | ||
|
|
||
| assert axd["B"].get_ylim() == (0, 50) | ||
| assert axd["D"].get_ylim() == (-10, 10) | ||
| assert axd["E"].get_ylim() != (0, 50) | ||
|
|
||
| axd["A"].set_xlim(-5, 5) | ||
| axd["C"].set_xlim(100, 200) | ||
|
|
||
| assert axd["E"].get_xlim() == (-5, 5) | ||
| assert axd["D"].get_xlim() != (100, 200) |
There was a problem hiding this comment.
While it's internal API, I think it's still clearer to test the actual grouping. Something like (didn't test the actual output order on this PR):
xgroups = [[ax.get_label() for ax in group] for group in Axes._shared_axes['x']]
assert xgroups == [["A", "E"], ["B", "F"]]
ygroups = [[ax.get_label() for ax in group] for group in Axes._shared_axes['y']]
assert ygroups == [["A", "B"], ["C", "D"], ["E", "F"]]
|
|
||
| msg = "must be True, False, 'all', 'none', 'row', or 'col'" | ||
| with pytest.raises(ValueError, match=msg): | ||
| fig.subplot_mosaic(layout, sharex="invalid_string") |
There was a problem hiding this comment.
Inline the layout as it's not really relevant.
| fig.subplot_mosaic(layout, sharex="invalid_string") | |
| fig.subplot_mosaic("AB;CD", sharex="invalid_string") |
| for ax in group: | ||
| # Float tolerance of 1e-5 to account for rounding errors | ||
| pos = ax.get_subplotspec().get_position(self) | ||
| if pos.ymin > bottom_edge + 1e-5: | ||
| ax.tick_params(labelbottom=False) |
| xgroups = [ | ||
| sorted(ax.get_label() for ax in group) | ||
| for group in axd["A"]._shared_axes["x"] | ||
| ] | ||
| assert ["A", "E"] in xgroups | ||
| assert ["B", "F"] in xgroups |
There was a problem hiding this comment.
I want the the full sharing information to be tested, in particular also that nothing else is shared. Let's go with sets instead of sorting to cope with order
| xgroups = [ | |
| sorted(ax.get_label() for ax in group) | |
| for group in axd["A"]._shared_axes["x"] | |
| ] | |
| assert ["A", "E"] in xgroups | |
| assert ["B", "F"] in xgroups | |
| xgroups = { | |
| {ax.get_label() for ax in group} | |
| for group in axd["A"]._shared_axes["x"] | |
| } | |
| assert xgroups ={{"A", "E"}, {"B", "F"}} |
also for ygroups.
Since we're going to need this several times, it makes sense to create a helper shared_groups(axis_name) so that tests reduce to one-liners:
assert shared_groups('x') == {{"A", "E"}, {"B", "F"}}
| def test_share_row_col_nested(self): | ||
| layout = [ | ||
| ["A", [["B", "C"], | ||
| ["D", "E"]]] | ||
| ] | ||
| fig = plt.figure() | ||
| axd = fig.subplot_mosaic(layout, sharey="row", sharex="col") |
There was a problem hiding this comment.
What exactly is this testing that may depend on nested?
I'd rather see some non-trivial layouts tested like.
AAB
CDD
EEF
Please also use the above mentioned group checks rather than checking (non)updated limits.
| # ax.sharey(ax0) | ||
| # ax._label_outer_yaxis(skip_non_rectangular_axes=True) | ||
|
|
||
| # Symmetric Sharing |
| ax_dict["A"].set(xscale="log", yscale="logit") | ||
| assert all(ax.get_xscale() == "log" and ax.get_yscale() == "logit" | ||
| for ax in ax_dict.values()) | ||
|
|
||
| # If sharing is True or "all", all axes should match "A" | ||
| if share_val in [True, "all"]: | ||
| assert all(ax.get_xscale() == "log" and ax.get_yscale() == "logit" | ||
| for ax in ax_dict.values()) | ||
| # If sharing is False or "none", only "A" should be changed | ||
| else: | ||
| assert ax_dict["A"].get_xscale() == "log" | ||
| assert ax_dict["B"].get_xscale() != "log" |
There was a problem hiding this comment.
Please rewrite this to group checks as well. That's much cleaner than the original scale checks.


PR summary
This PR implements string arguments (
'all','row','col') and booleanTrue/Falseforsharexandshareyinsubplot_mosaic()for #18305 .To handle complex and nested layouts, the axes are grouped based on their specific
GridSpecand exactrowspan/colspancoordinates. Axes only share a row or column if their spans are identical.For example, consider a layout where C and D span both rows:
If
sharey='row'is passed, the code forms three isolated sharing groups based on their vertical spans:[A, B](Row 0)[E, F](Row 1)[C, D](Spanning Rows 0 and 1)Once the groups are isolated, the logic designates the first axis in each list as the parent and calls standard
.sharex(parent)or.sharey(parent)on the remaining children in that specific group.Important Note: Relying on Matplotlib's global
ax._label_outer_xaxis()orax._label_outer_yaxis()inadvertently hid tick labels for inner subplots if they formed their own isolated sharing group. For this very reason, I implemented a custom logic which calculates label visibility dynamically per group. For example, in an X-axis sharing group, it calculates the physical bottom edge by finding themax()ofrowspan.stopacross just the group's members. It then loops through that group and appliesax.tick_params(labelbottom=False)to any axis sitting above that localized bottom edge.AI Disclosure
I have used Generative AI to understand the codebase (specifically the code for the issue), setting up of the environment (installing packages and dependencies required), the intent of the issue and to get to the center of the problem. It is also important to mention that AI was used to understand the requirements of the maintainers and how #32239 failed and what to avoid while coding.
Verification
I have tested the code for a local matplotlib development build, running it on my system and creating a sample plot for a sample data. Also, I have written some test cases for the same by trying to incorporate as many test cases as possible (5), all of which successfully passed.
I would be happy for any feedback regarding any portion of the code!!
PR quality check