You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: ax.invert_xaxis() and ax.invert_yaxis() both flip the X axis #21369
The 3D wrappers in lib/mpl_toolkits/mplot3d/axis3d.py are a bit funny. XAxis, YAxis, ZAxis all derive from Axis, which derives from maxis.XAxis. The directional Axis classes override view/data interval setters/getters, so calling set_xlim, etc. work correctly. But set_inverted is not overridden, and calls the x version. Probably there are other methods that are incorrect like that, but happen to work by fluke, or no-one noticed.
Labeling this as a good first issue because there is a proposed patch and it does not involve any API design choices, however this is not a good issue for someone new-to-programming as it will require chasing through some multiple inheritance issues ;) .
@QuLogic Could you please tell me how it breaks the y-ticks? I tried out the patch but couldn't figure it out.
Also, I would like to work on this issue further and help solve it.
I examined this and think that it is actually the tick position which is appearing to throw the padding off. For x-axis, the tick position can be either top or bottom. For the y-axis, it can be left or right. By default, it is set to left (as this is how most 2D plots are oriented).
If you adopt the fix suggested by @QuLogic (fixing inheritance in 3D axes) and add ax.yaxis.set_ticks_position("right") to the plot commands, the spacing is fixed and the inversion is correct.
from mpl_toolkits import mplot3d
import matplotlib.pyplot as plt
ax = plt.axes(projection='3d')
plt.title("Invert Z")
ax.scatter3D(1,1,1)
# ax.invert_xaxis()
ax.invert_yaxis()
ax.yaxis.set_ticks_position("right")
A solution to this may be to create 3D yaxes with different tick and label positions. However, the needed position for the axis will likely depend on the viewing angle of the 3D plot. It does perhaps make sense though, to set the default positions for the 3D y axis to work well with the default viewing angle.
I'd like to submit a pull request to fix this bug, but I may need some additional help on how best to set the position of the ticks for the y axis. Currently, the y-axis for 3D plots looks like this:
Would an advisable approach be to override the __init__ method of this class to set the tick positions? I have tried this, but it doesn't successfully change the tick positions.
There are a couple of possible gotcha's in the mplot3D code base. The most 😱 of which is that we do some type promotion where we init the 2D version of the object and then reset the obj.__class__ to make it the 3D version. Another possibility is that in the Axes3D.__init__ we may be re-setting the tick location explicitly someplace else. A third possible thing that could be going wrong is that not all of the classes in the MRO are using super() in their __init__ so the call chain is being broken at some point (either intentionally or accidentally).
My next debugging step here would be to override set_ticks_position like
Thanks for the tip @tacaswell - it was going through the constructor, but a lot more happens after that. I added the command at a point after the axes are created, may be a bandaid fix. PR opened, lots of tests failing at the moment.
So, here's what I've found about the problem that prevents you
from fixing the ticks position.
I tried fixing it by making an init method in the "YAxis"
class in "mpl_toolkits\mplot3d\axis3d.py". The problem is :
this init method will be called before the _get_tick method in
the "YAxis" class in "matplotlib\axis.py". So all the changes
I make are being rewritten, and nothing changes in the end.
The only way I found to bypass this problem is by doing this :
-adding this method in "matplotlib\axis.py" -> YTick class :
-adding these lines in "mpl_toolkits\mplot3d\axis3d.py" -> YAxis class :
...
get_data_interval, set_data_interval = maxis._make_getset_interval(
"data", "xy_dataLim", "intervaly")
def __init__(self,*args,**kwargs): # added line
super().__init__(*args,is3d=True,**kwargs) # added line
This is in fact just checking if the projection is 2D or 3D, in order to
change the tick alignments if needed.
and everything works just fine. (including invert.yaxis())
At least I think : I don't know how to check the safety of these addings
properly.
@coding-krtek - You can check your solution by running the unit tests. If you have pytest (pip install pytest), you can run the tests by typing pytest into command line when you are in your cloned repo. There are a number of tests which have reference images, and if your solution results in a difference from the stored reference images, the tests will fail. After running the tests, images from the tests will be in a folder called result_images
In my PR for this issue #21424, I fixed the axis inheritance and have changed the behavior in the constructor of Axes3D to change the default side the y axis label is on. This solution is not yet accepted as changing axis inheritance causes changes in the reference images. For your solution did you adopt the proposed patch of fixing the classes which the 3D axes inherit from?
I can't speak for the repo maintainers, but my thought would be that you do not want to change parts of the code (for example YAxis and YTick classes) which don't have anything to do with this issue unless there is no way to avoid it. To rephrase - I think it would be better to keep changes isolated to 3D axis code (closer to the problem, maybe won't be as confusing to someone looking at the code later).
Um I'll be honest, I was just changing the matplotlib files directly, and I don't know how cloned repositories or virtual environments work.
Yes, I changed the inheritance ...(Axis, maxis.YAxis).
But it looks that the default 2D classes have the last word on the ticks parameters, so I don't know how to solve the problem without changing them.
Edit
Okay so I was wrong, there is a way of fixing the problem without touching the 2D classes :
in "mpl_toolkits\mplot3d\axis3d.py" -> YAxis class :
...
class YAxis(Axis, maxis.YAxis):
get_view_interval, set_view_interval = maxis._make_getset_interval(
"view", "xy_viewLim", "intervaly")
get_data_interval, set_data_interval = maxis._make_getset_interval(
"data", "xy_dataLim", "intervaly")
#------added lines---------------
def fix(self):
self.majorTicks[0].label1.set(horizontalalignment='center',
verticalalignment='top')
self.minorTicks[0].label1.set(horizontalalignment='center',
verticalalignment='top')
self.majorTicks[0].label2.set(horizontalalignment='center',
verticalalignment='top')
self.minorTicks[0].label2.set(horizontalalignment='center',
verticalalignment='top')
# If you try to change the position in an __init__ method, it
# will be called too soon and the changes will be lost.
#--------------------------------
and then in "mpl_toolkits\mplot3d\axes3d.py" -> Axes3D class, in the "cla" method :
And this time "everything" (except 11 tests) works, both inheritance and ticks. It's really a duct-tape solution but I don't think there's a way of fixing this issue by changing only the YAxis class and nothing else.
Hey @coding-krtek - if you'd like to contribute your solution to matplotlib you'll need to fork the repository and make a pull request. This way, others can see and try out your changes and they might be accepted into the main matplotlib library. You can join the gitter and ask to be put in the "incubator" channel to get help with this - https://gitter.im/matplotlib/matplotlib
Activity
QuLogic commentedon Oct 15, 2021
The 3D wrappers in
lib/mpl_toolkits/mplot3d/axis3d.pyare a bit funny.XAxis,YAxis,ZAxisall derive fromAxis, which derives frommaxis.XAxis. The directional Axis classes override view/data interval setters/getters, so callingset_xlim, etc. work correctly. Butset_invertedis not overridden, and calls the x version. Probably there are other methods that are incorrect like that, but happen to work by fluke, or no-one noticed.QuLogic commentedon Oct 15, 2021
Something like the following fixes the binding problem:
but it breaks the Y ticks, probably because of some assumption elsewhere.
tacaswell commentedon Oct 15, 2021
Labeling this as a good first issue because there is a proposed patch and it does not involve any API design choices, however this is not a good issue for someone new-to-programming as it will require chasing through some multiple inheritance issues ;) .
DebadityaPal commentedon Oct 19, 2021
@QuLogic Could you please tell me how it breaks the y-ticks? I tried out the patch but couldn't figure it out.
Also, I would like to work on this issue further and help solve it.
QuLogic commentedon Oct 19, 2021
The padding is wrong, and sometimes extra grid lines are drawn that shouldn't be. Run the tests and you will see what breaks.
janash commentedon Oct 20, 2021
I examined this and think that it is actually the tick position which is appearing to throw the padding off. For x-axis, the tick position can be either
toporbottom. For the y-axis, it can beleftorright. By default, it is set toleft(as this is how most 2D plots are oriented).If you adopt the fix suggested by @QuLogic (fixing inheritance in 3D axes) and add
ax.yaxis.set_ticks_position("right")to the plot commands, the spacing is fixed and the inversion is correct.A solution to this may be to create 3D yaxes with different tick and label positions. However, the needed position for the axis will likely depend on the viewing angle of the 3D plot. It does perhaps make sense though, to set the default positions for the 3D y axis to work well with the default viewing angle.
I'd like to submit a pull request to fix this bug, but I may need some additional help on how best to set the position of the ticks for the y axis. Currently, the y-axis for 3D plots looks like this:
Would an advisable approach be to override the
__init__method of this class to set the tick positions? I have tried this, but it doesn't successfully change the tick positions.This is what I have tried:
and after creating the plots, using the command
ax.yaxis.get_ticks_position()still results inleftbeing returned.Image attached showing that using

ax.yaxis.set_ticks_position("right")fixes paddingtacaswell commentedon Oct 21, 2021
There are a couple of possible gotcha's in the mplot3D code base. The most 😱 of which is that we do some type promotion where we init the 2D version of the object and then reset the
obj.__class__to make it the 3D version. Another possibility is that in theAxes3D.__init__we may be re-setting the tick location explicitly someplace else. A third possible thing that could be going wrong is that not all of the classes in the MRO are usingsuper()in their__init__so the call chain is being broken at some point (either intentionally or accidentally).My next debugging step here would be to override
set_ticks_positionliketo sort out where it is being called and to add either breakpoints or prints to the new
__init__to make sure it is really being called.fix y axis on 3d plot
janash commentedon Oct 21, 2021
Thanks for the tip @tacaswell - it was going through the constructor, but a lot more happens after that. I added the command at a point after the axes are created, may be a bandaid fix. PR opened, lots of tests failing at the moment.
berry-pixel commentedon Oct 30, 2021
Is this issue still open? I've been looking for a starting point for contribution to open source
coding-krtek commentedon Dec 4, 2021
Hello !
So, here's what I've found about the problem that prevents you
from fixing the ticks position.
I tried fixing it by making an init method in the "YAxis"
class in "mpl_toolkits\mplot3d\axis3d.py". The problem is :
this init method will be called before the _get_tick method in
the "YAxis" class in "matplotlib\axis.py". So all the changes
I make are being rewritten, and nothing changes in the end.
The only way I found to bypass this problem is by doing this :
-adding this method in "matplotlib\axis.py" -> YTick class :
-adding this line in "matplotlib\axis.py" -> YAxis class in the init :
-rewriting the _get_tick method in "matplotlib\axis.py" -> YAxis class :
-adding these lines in "mpl_toolkits\mplot3d\axis3d.py" -> YAxis class :
This is in fact just checking if the projection is 2D or 3D, in order to
change the tick alignments if needed.
and everything works just fine. (including invert.yaxis())
At least I think : I don't know how to check the safety of these addings
properly.
janash commentedon Dec 5, 2021
@coding-krtek - You can check your solution by running the unit tests. If you have pytest (
pip install pytest), you can run the tests by typingpytestinto command line when you are in your cloned repo. There are a number of tests which have reference images, and if your solution results in a difference from the stored reference images, the tests will fail. After running the tests, images from the tests will be in a folder calledresult_imagesIn my PR for this issue #21424, I fixed the axis inheritance and have changed the behavior in the constructor of
Axes3Dto change the default side the y axis label is on. This solution is not yet accepted as changing axis inheritance causes changes in the reference images. For your solution did you adopt the proposed patch of fixing the classes which the 3D axes inherit from?I can't speak for the repo maintainers, but my thought would be that you do not want to change parts of the code (for example
YAxisandYTickclasses) which don't have anything to do with this issue unless there is no way to avoid it. To rephrase - I think it would be better to keep changes isolated to 3D axis code (closer to the problem, maybe won't be as confusing to someone looking at the code later).coding-krtek commentedon Dec 5, 2021
Um I'll be honest, I was just changing the matplotlib files directly, and I don't know how cloned repositories or virtual environments work.
Yes, I changed the inheritance
...(Axis, maxis.YAxis).But it looks that the default 2D classes have the last word on the ticks parameters, so I don't know how to solve the problem without changing them.
Edit
Okay so I was wrong, there is a way of fixing the problem without touching the 2D classes :
And this time "everything" (except 11 tests) works, both inheritance and ticks. It's really a duct-tape solution but I don't think there's a way of fixing this issue by changing only the YAxis class and nothing else.
janash commentedon Dec 6, 2021
Hey @coding-krtek - if you'd like to contribute your solution to matplotlib you'll need to fork the repository and make a pull request. This way, others can see and try out your changes and they might be accepted into the main matplotlib library. You can join the gitter and ask to be put in the "incubator" channel to get help with this - https://gitter.im/matplotlib/matplotlib
baloe commentedon Dec 7, 2021
I also noticed that
get_ticks_position()returnsbottomfor all three axes.baloe commentedon Dec 14, 2021
For those in need for a workaround: I discovered that the following lines actually do invert the yaxis:
anntzer commentedon May 27, 2022
Closed by #21442.