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]: Issue with setting axis limits on 3D plots #25804
I have created 3D surface plots using matplotlib as described [here]. Everything works except when I set the axis limits. Then the plots look very strange. It appears that commands such as Axes.set_xlim() (whether or not Axes.set_xbound()is used) limit the axis but not the data. The data is plotted and appears to continue beyond the axis limits.
I think that the axes not "truncating" the plotted surfaces is the intended behavior, though an option to crop data to the box inside the axis planes would be useful. Right now clip_box only clips the 2D projection, and I think clip_box_3d or something similar would be a worthwhile feature.
Somewhat related to #23450, where the issue is reversed - there we want to be clipping based on the 3d box, not the 2d one.
A workaround in your case might be to edit your data so that points higher than 10 / 3000 on the respective axes are set to np.nan and are thus hidden. This may require calculating the interpolated contour line at 3000 and manually adding those points back to your dataset, so you don't get a jagged "sawtooth" there.
Thanks. I really don't understand why this is the intended behavior because it sure doesn't make sense and I wasted a lot of time trying to change the behavior. I just want to be able to set the axis limit without modifying the data. Any chance this could be fixed?
Hi @nbfazel thank you for raising the issue! I'll try to take a look at this, but fair warning that it might not be soon and matplotlib only releases a new version every 6-12 months or so.
I see two ways to implement this:
Extend bounding boxes to 3D, which I think would be a good bit more work but is a little more flexible.
Have a clip_to_axis_bounds flag (or similar) which does specifically this behavior.
I think (2) is definitely much easier and covers most of the desired use cases here, and could always be a special case of (1) if that ever gets implemented.
I did some digging into this and think it will end up being rather complicated for surfaces plots like your example. The problem is that matplotlib's 3D renderer does not use a ray-tracing algorithm, so you cannot easily threshold drawing only part of a face for a polygon between specific bounds.
Take (A) as your initial set of points. It would be fairly easy to threshold your points / vertices based on bounds, and I think that would work well for scatter plots. In the case of surfaces however, this will likely mean jagged sawtooth edges since you would not be drawing the polygons which have a vertex right outside your bounds, as in (B). Perhaps that's okay as a first step implementation.
The way to get around this would be to fill in the gaps with new polygons created on the fly to edge match with the bounds, as in (C). That however could get very complicated especially in 3D near the corners/edges of the bounds from multiple axes, or in cases where your bounds are tighter than the extent of a single polygon.
In this example (scroll down to the answer), limits are set for all axes, without running into the issues I described above. I thought maybe it was because it also set axes to off. I tried that and it didn't make a difference. I'm not sure why setting axis limits works in the example. (I ran the example and produce the same image.)
The issue persists in the example above. If you don't turn the axes off, then this is more visible:
If you zoom out, you can see that the limits weren't truncated for the data (500/(9*pi) = 17.68):
MATLAB and Mathematica use their own renderers, I expect automatically cropping data in 3D space would be quite a bit of effort to bring into matplotlib.
Thank you so much. So do you think I should just go with the advice you gave on May 2? ("edit your data so that points higher than 10 / 3000 on the respective axes are set to np.nan")
If you need to make these plots any time soon, then yes. With matplotlib as a 99%-volunteer effort it can take quite a long time to implement feature requests, and the number of people familiar with the 3D code is a smaller fraction than the 2D plotting. If you want to take a shot at adding this though, I think that an option to clip 3D data to the axis limits would be a welcome feature. :)
Thanks! Recently I tried to fix an issue with SciPy (that I had reported) and even though the fix wasn't complicated, I spent a lot of time trying to set up my development environment and ultimately gave up. This is more challenging since I would first need to familiarize myself with 3D rendering and then again try to set up my development environment.
I did a dive into stackoverflow, and it looks like this a very common use case. I think we should go ahead and implement a flag to clip to 3D axis bounds now, and we can figure out how to clean up jagged edges later.
LIke the suggestion by @scottshambaugh what if matplotlib chose the make the plots using the copy of the original numpy array with data points set to np.nan based on the choice of limits? Would that be easy to implement within matplotlib itself?
Draft PR is up to add this functionality, I'll be fleshing it out over the next week.
That PR will implement option (B) from my comment above, with jagged edges at the boundary. I'm going to say that getting straight/smooth edges rather than jagged edges is a "won't do" feature for now, as it would require some pretty drastic changes to how the renderer works, so (C) won't happen and I'll close this issue once the PR is merged. For further tracking of the smooth edges question, that bit can be considered a duplicate of this question: #8902
Activity
scottshambaugh commentedon May 2, 2023
I think that the axes not "truncating" the plotted surfaces is the intended behavior, though an option to crop data to the box inside the axis planes would be useful. Right now
clip_boxonly clips the 2D projection, and I thinkclip_box_3dor something similar would be a worthwhile feature.Somewhat related to #23450, where the issue is reversed - there we want to be clipping based on the 3d box, not the 2d one.
scottshambaugh commentedon May 2, 2023
A workaround in your case might be to edit your data so that points higher than 10 / 3000 on the respective axes are set to
np.nanand are thus hidden. This may require calculating the interpolated contour line at 3000 and manually adding those points back to your dataset, so you don't get a jagged "sawtooth" there.nbfazel commentedon May 3, 2023
Thanks. I really don't understand why this is the intended behavior because it sure doesn't make sense and I wasted a lot of time trying to change the behavior. I just want to be able to set the axis limit without modifying the data. Any chance this could be fixed?
scottshambaugh commentedon May 5, 2023
Hi @nbfazel thank you for raising the issue! I'll try to take a look at this, but fair warning that it might not be soon and matplotlib only releases a new version every 6-12 months or so.
I see two ways to implement this:
clip_to_axis_boundsflag (or similar) which does specifically this behavior.I think (2) is definitely much easier and covers most of the desired use cases here, and could always be a special case of (1) if that ever gets implemented.
nbfazel commentedon May 5, 2023
Thank you @scottshambaugh. I appreciate that. I will look for a interim solution while this is being implemented.
scottshambaugh commentedon May 9, 2023
I did some digging into this and think it will end up being rather complicated for surfaces plots like your example. The problem is that matplotlib's 3D renderer does not use a ray-tracing algorithm, so you cannot easily threshold drawing only part of a face for a polygon between specific bounds.
Take (A) as your initial set of points. It would be fairly easy to threshold your points / vertices based on bounds, and I think that would work well for scatter plots. In the case of surfaces however, this will likely mean jagged sawtooth edges since you would not be drawing the polygons which have a vertex right outside your bounds, as in (B). Perhaps that's okay as a first step implementation.
The way to get around this would be to fill in the gaps with new polygons created on the fly to edge match with the bounds, as in (C). That however could get very complicated especially in 3D near the corners/edges of the bounds from multiple axes, or in cases where your bounds are tighter than the extent of a single polygon.
nbfazel commentedon May 11, 2023
Thanks for the update. I wonder how this is done Matlab or Mathematica.
nbfazel commentedon May 21, 2023
In this example (scroll down to the answer), limits are set for all axes, without running into the issues I described above. I thought maybe it was because it also set axes to off. I tried that and it didn't make a difference. I'm not sure why setting axis limits works in the example. (I ran the example and produce the same image.)
nbfazel commentedon Aug 1, 2023
Just checking to see if we know why in the example provided above (on May 21), they don't run into the problem after setting axis limit in 3D. Thanks!
scottshambaugh commentedon Aug 1, 2023
The issue persists in the example above. If you don't turn the axes off, then this is more visible:

If you zoom out, you can see that the limits weren't truncated for the data (500/(9*pi) = 17.68):

MATLAB and Mathematica use their own renderers, I expect automatically cropping data in 3D space would be quite a bit of effort to bring into matplotlib.
nbfazel commentedon Aug 1, 2023
Thank you so much. So do you think I should just go with the advice you gave on May 2? ("edit your data so that points higher than 10 / 3000 on the respective axes are set to np.nan")
scottshambaugh commentedon Aug 1, 2023
If you need to make these plots any time soon, then yes. With matplotlib as a 99%-volunteer effort it can take quite a long time to implement feature requests, and the number of people familiar with the 3D code is a smaller fraction than the 2D plotting. If you want to take a shot at adding this though, I think that an option to clip 3D data to the axis limits would be a welcome feature. :)
nbfazel commentedon Aug 2, 2023
Thanks! Recently I tried to fix an issue with SciPy (that I had reported) and even though the fix wasn't complicated, I spent a lot of time trying to set up my development environment and ultimately gave up. This is more challenging since I would first need to familiarize myself with 3D rendering and then again try to set up my development environment.
scottshambaugh commentedon Sep 18, 2023
I did a dive into stackoverflow, and it looks like this a very common use case. I think we should go ahead and implement a flag to clip to 3D axis bounds now, and we can figure out how to clean up jagged edges later.
This question shows how to do this manually using MaskedArrays, which I think will make a for a good approach here. Will have to think about recalculating this on zoom changes, might slow down interactivity quite a bit. https://stackoverflow.com/questions/56411587/matplotlib-3d-plot-how-to-use-set-zlim-correctly as a way to do this manually
I found the following issues where this behavior was desired:
harish-srini commentedon Nov 2, 2023
LIke the suggestion by @scottshambaugh what if matplotlib chose the make the plots using the copy of the original numpy array with data points set to np.nan based on the choice of limits? Would that be easy to implement within matplotlib itself?
scottshambaugh commentedon Nov 20, 2023
Draft PR is up to add this functionality, I'll be fleshing it out over the next week.
That PR will implement option (B) from my comment above, with jagged edges at the boundary. I'm going to say that getting straight/smooth edges rather than jagged edges is a "won't do" feature for now, as it would require some pretty drastic changes to how the renderer works, so (C) won't happen and I'll close this issue once the PR is merged. For further tracking of the smooth edges question, that bit can be considered a duplicate of this question: #8902
nbfazel commentedon Nov 20, 2023
OK, thank you. I'll give it a try when it becomes available.
scottshambaugh commentedon Nov 26, 2023
#27349 to implement dynamic clipping in 3D plots is now ready for review if you'd like to play with it!
nbfazel commentedon Nov 26, 2023
Thanks! What do I need to do to sync up and pull the version of the code with the fix?
scottshambaugh commentedon Nov 26, 2023
@nbfazel you can find the branch that is getting merged here: https://github.com/scottshambaugh/matplotlib/tree/3d_dynamic_masking
And this stackoverflow answer has good directions on the commands to do that: https://stackoverflow.com/questions/14383212/git-pulling-a-branch-from-another-repository
But hopefully that issue has enough info to evaluate the behavior without having to download and test locally.