Fix #21409: Make twin axes inherit parent position#31353
Fix #21409: Make twin axes inherit parent position#31353maf2310 wants to merge 4 commits intomatplotlib:mainfrom
Conversation
When set_position() is called before twinx() or twiny(), the twin axes are created using the original subplot position instead of the current position of the parent. Set the twin position from the parent in _make_twin_axes so that twins start aligned with the parent axes. Add a regression test covering both twinx() and twiny().
|
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 gitter 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. |
|
⏰ This pull request might be automatically closed in two weeks from now. Thank you for your contribution to Matplotlib and for the effort you have put into this PR. This pull request does not yet meet the quality and clarity standards needed for an effective review. Project maintainers have limited time for code reviews, and our goal is to prioritize well-prepared contributions to keep Matplotlib maintainable. Matplotlib maintainers cannot provide one-to-one guidance on this PR. However, if you ask focused, well-researched questions, a community member may be willing to help. 💬 To increase the chance of a productive review:
As the author, you are responsible for driving this PR, which entails doing necessary background research as well as presenting its context and your thought process. If you are a new contributor, or do not know how to fulfill these requirements, we recommend that you familiarize yourself with Matplotlib's development conventions or engage with the community via our Discourse or one of our meetings before submitting code. If you substantially improve this PR within two weeks, leave a comment and a team member may remove the |
|
Per your comment in #21409, please test this with |
|
Thanks! I tested this with Without With So this minimal fix addresses the original issue at twin creation time, but it |
|
Could you please share some screenshots that show what's going on? |
|
Yes, of course
Here are the screenshots showing the behavior before and after the fix, as well as with Please let me know if this behavior is acceptable or if we need to explore further. |
|
Thanks for the patch and test! One concern: twin.set_position(...) calls set_in_layout(False), which opts all twins out of layout even for normal subplots. That’s a behavior change beyond the bug. Could we either gate this to only run when the parent is manually positioned (not self.get_in_layout()), or use _set_position(...) so layout participation doesn’t change? That would keep the fix focused on the reported issue without altering layout behavior. |
|
Thanks, that’s a very helpful point — I hadn’t considered the impact on You're right that using I’ll update the patch to avoid that behavior change. My plan is to either:
I’ll test both approaches (including with |
|
I’ve updated the patch based on your suggestion. The fix is now limited to the manual-positioning case I reran the relevant tests (including the original reproduction case and |
lib/matplotlib/axes/_base.py
Outdated
| if not self.get_in_layout(): | ||
| twin.set_position(self.get_position()) |
There was a problem hiding this comment.
Thanks for the update the not self.get_in_layout() guard looks right and keeps the fix scoped to manual positioning. One small concern: twin.set_position(self.get_position()) sets both original and active positions. If the parent only changed the active position (set_position(..., which="active")), this would also overwrite the twin’s original.
Should we consider _set_position(..., which="active") or explicitly using get_position(original=True) depending on intended semantics?
lib/matplotlib/tests/test_axes.py
Outdated
| @pytest.mark.parametrize("twin", ("x", "y")) | ||
| def test_twin_respects_position_after_set_position(twin): | ||
| fig, ax = plt.subplots() | ||
|
|
||
| ax.set_position([0.2, 0.2, 0.5, 0.5]) | ||
| ax2 = getattr(ax, f"twin{twin}")() | ||
|
|
||
| assert_allclose(ax.bbox.bounds, ax2.bbox.bounds) |
There was a problem hiding this comment.
Also, the new test compares ax.bbox.bounds without forcing a draw; that can be backend‑sensitive. Would you consider comparing ax.get_position().bounds instead, or calling fig.canvas.draw() before comparing bboxes?
|
@maf2310 Linting check is failing consider fixing it also! |
|
Thanks for the detailed feedback! I updated the patch so that it now:
I reran the relevant local tests and the original reproduction case. The remaining CI failures appear to be in unrelated areas:
There are no failures in |
timhoffm
left a comment
There was a problem hiding this comment.
Overall, I suspect the need for distinguishing in-layout is an indicator of a deeper architectual issue, that we need to work around here. Therefore, while this fixes the concrete issue, I'm not convinced that it's the right solution.
|
Thanks, that makes sense. I see your point about this possibly being a deeper issue. For now I tried to keep the fix focused on the specific case from the Happy to adjust if you think there’s a better approach. |
|
I don't know whether there is a better approach. It might be that we are forced to this "workaround" because of the current architecture of the library (layout takes responsibility of position management, and we have to do this manually in the absence of layout - could be a valid argument). But I would like this to be explicitly investigated and discussed. In particular, I'd like to have @jklymak's feedback here as he is our layout expert and the author of #22817. |



Closes #21409
PR summary
When
set_position()is called beforetwinx()ortwiny(), the twinaxes are created using the original subplot position instead of the
current position of the parent axes.
This causes the twin axes to be misaligned with the parent.
This change ensures that the twin axes inherit the current position of
the parent at creation time by calling:
This is a minimal fix that avoids changing the subplot or layout model
and instead enforces consistency at twin creation time.
A regression test was added for both
twinx()andtwiny(). It checksthat the parent and twin axes have matching
bbox.boundsafter callingset_position()before creating the twin.Minimum example
AI Disclosure
AI assistance was used to help understand parts of the existing
Matplotlib codebase during investigation. The final patch and tests were
implemented and validated manually.
PR checklist