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
[ENH]: Separate autoscale_x_left and autoscale_x_right (and likewise for y) #30585
Currently, one can control separately whether autoscaling is applied to the x-axis and to the y-axis, but not whether it is only applied to the left or the right of the x-axis (or likewise to the bottom or the top of the y-axis). Yet this would be useful, e.g. in the case where the left limit is forced to zero whereas the right limit should follow autoscaling.
yet if I want to force the left x-limit to 0 while leaving the right one autoscaled, I have to put the set_xlim() call after all the plotting, which splits axes configuration in two places. (One can also put the entire ax.set(...) call at the bottom, but this can be less clear especially when there are multiple axes being set up, as ax.set(title=...) also serves as self-commenting code to introduce what this axes will be about.)
Proposed solution
Make it possible to separately control whether the left and the right x-limits are autoscaled, and ideally make ax.set_xlim(0, None) only turn off autoscaling on the left x-limit (via the relevant deprecation process).
Note that one question is how to compute the autoscaled right limit when the left limit is already forced. For simplicity, I believe this should behave as if the left limit wasn't forced, i.e. the right limit should be equal to the maximum value plus a margin equal to 0.05 (or whatever has been configured) times the entire x-span of the data; this way the behavior is as if ax.set_xlim(0, None) had been called after the data is plotted. An alternative would be to not use the entire x-span, but only the span of data from x=0 to the maximum value (this would differ if some x-values are negative).
I'm Alverok and I was looking into this enhancement request (Issue #30585) as a potential first contribution to understand the Matplotlib codebase. I've performed some testing on the current version of Matplotlib (3.8.3 in my environment after a dev install, which is what the editable install would have given you).
It appears that the behavior described in the "Proposed solution"—specifically, where the autoscaled limit uses a margin calculated from the entire data span even when one limit is fixed—might already be the current default behavior.
Here's a test script I used to investigate:
importmatplotlib.pyplotaspltimportnumpyasnpfig, (ax1, ax2, ax3) =plt.subplots(3, 1, figsize=(6, 9))
# --- Scenario 1: Data starts above forced limit (original poster's example type) ---# Data range: [2, 10], Span: 8. Expected 5% margin: 0.4.# Total autoscaled range from data: [1.6, 10.4] (if fully autoscaled)ax1.plot(np.linspace(2, 10, 100), np.sin(np.linspace(2, 10, 100)))
ax1.set_xlim(left=0, right=None)
ax1.set_title("Data: [2, 10], xlim(0, None)")
# --- Scenario 2: Data with negative values (explicitly testing span calculation) ---# Data range: [-5, 10], Span: 15. Expected 5% margin: 0.75.# Total autoscaled range from data: [-5.75, 10.75] (if fully autoscaled)ax2.plot(np.linspace(-5, 10, 100), np.sin(np.linspace(-5, 10, 100)))
ax2.set_xlim(left=0, right=None) # Left forced to 0ax2.set_title("Data: [-5, 10], xlim(0, None)")
# --- Scenario 3: Fully autoscaled for direct comparison ---# Same data as Scenario 2, but no explicit xlim set.ax3.plot(np.linspace(-5, 10, 100), np.sin(np.linspace(-5, 10, 100)))
ax3.set_title("Data: [-5, 10], Fully Autoscaled")
plt.tight_layout()
plt.show()
Here is the resulting plot from the test script:
As the plot shows:
For Scenario 1 (Data [2, 10], xlim(0, None)): The x-axis limits are [0, 10.4]. The right limit correctly includes a 5% margin (0.4) calculated from the data's span (10 - 2 = 8).
For Scenario 2 (Data [-5, 10], xlim(0, None)): The x-axis limits are [0, 10.75]. Here, the right limit correctly includes a 5% margin (0.75) calculated from the full data span (10 - (-5) = 15), even though the left limit is manually set to 0. This directly addresses the nuance mentioned in the issue: "this should behave as if the left limit wasn't forced, i.e. the right limit should be equal to the maximum value plus a margin... times the entire x-span of the data."
For Scenario 3 (Data [-5, 10], Fully Autoscaled): The x-axis limits are [-5.75, 10.75], showing the full autoscaling behavior for comparison.
This observed behavior directly aligns with the "Proposed solution" described in the issue, particularly regarding how the None limit should be calculated using the full data range for margins.
Upon further investigation of the codebase, I traced the logic to lib/matplotlib/axis.py, within the Axis._set_lim method. The relevant section that handles None limits is:
This code explicitly retrieves old0 and old1 (which are the fully autoscaled limits determined by Axes.autoscale_view() during the drawing process) and substitutes them if None was passed. This effectively re-uses the full autoscale calculation for the None side.
Given these findings, it seems the desired behavior outlined in this enhancement request is already present in the current version of Matplotlib. Am I misunderstanding a more subtle aspect of the request, or is it possible this has been implemented since the issue was opened?
Hi @Alverok thank you for your willingness to work on this. I think the feature request is about what happens if you set the limits before calling plot. In that case you do not get the desired result.
As an aside, the most recent release of Matplotlib is v3.10.6. If you installed from an up-to-date git checkout you should get a version that starts 3.11.0.dev. So something seems to have gone wrong there.
Hi, @rcomer, thank you so much for the clarification and the heads-up about my environment! I've synced my repository to the latest main branch, corrected my setup, and I'm now on the correct development version - 3.11.0.dev1400+g652012d5b.
With the updated environment, I can now confirm the behavior you described when reversing the order of operations. I ran a test script where one plot sets the limits before plotting, and the other sets them after.
# new_test_plot.pyimportmatplotlib.pyplotaspltimportnumpyasnpfig, (ax1, ax2) =plt.subplots(2, 1, figsize=(6, 6))
# --- Scenario 1: set_xlim BEFORE plot ---# This demonstrates the issue.ax1.set_xlim(left=0, right=None) # Set limit on an empty plotax1.plot(np.linspace(2, 10, 100), np.sin(np.linspace(2, 10, 100)))
ax1.set_title("Scenario 1: set_xlim() BEFORE plot()")
# --- Scenario 2: set_xlim AFTER plot (for comparison) ---# This shows the currently working behavior.ax2.plot(np.linspace(2, 10, 100), np.sin(np.linspace(2, 10, 100)))
ax2.set_xlim(left=0, right=None) # Set limit after data is presentax2.set_title("Scenario 2: set_xlim() AFTER plot() (Original Test)")
plt.tight_layout()
plt.show()
This script produces the following plot:
As you can see in Scenario 1, when set_xlim is called on the empty axes, the view is fixed to a default [0, 1] range, and the subsequently plotted data (from x=2 to x=10) is not visible. This confirms the issue perfectly.
I'd be happy to try and investigate a solution. Do you have any pointers on where in the codebase one might start to implement the "sticky" None behavior for autoscaling?
Activity
Alverok commentedon Oct 2, 2025
Hello everyone,
I'm Alverok and I was looking into this enhancement request (Issue #30585) as a potential first contribution to understand the Matplotlib codebase. I've performed some testing on the current version of Matplotlib (
3.8.3in my environment after a dev install, which is what the editable install would have given you).It appears that the behavior described in the "Proposed solution"—specifically, where the autoscaled limit uses a margin calculated from the entire data span even when one limit is fixed—might already be the current default behavior.
Here's a test script I used to investigate:
Here is the resulting plot from the test script:
As the plot shows:
This observed behavior directly aligns with the "Proposed solution" described in the issue, particularly regarding how the None limit should be calculated using the full data range for margins.
Upon further investigation of the codebase, I traced the logic to
lib/matplotlib/axis.py, within theAxis._set_limmethod. The relevant section that handles None limits is:This code explicitly retrieves
old0andold1(which are the fully autoscaled limits determined byAxes.autoscale_view()during the drawing process) and substitutes them if None was passed. This effectively re-uses the full autoscale calculation for the None side.Given these findings, it seems the desired behavior outlined in this enhancement request is already present in the current version of Matplotlib. Am I misunderstanding a more subtle aspect of the request, or is it possible this has been implemented since the issue was opened?
Thanks for your time and guidance!
rcomer commentedon Oct 2, 2025
Hi @Alverok thank you for your willingness to work on this. I think the feature request is about what happens if you set the limits before calling
plot. In that case you do not get the desired result.As an aside, the most recent release of Matplotlib is v3.10.6. If you installed from an up-to-date git checkout you should get a version that starts 3.11.0.dev. So something seems to have gone wrong there.
Alverok commentedon Oct 2, 2025
Hi, @rcomer, thank you so much for the clarification and the heads-up about my environment! I've synced my repository to the latest
mainbranch, corrected my setup, and I'm now on the correct development version -3.11.0.dev1400+g652012d5b.With the updated environment, I can now confirm the behavior you described when reversing the order of operations. I ran a test script where one plot sets the limits before plotting, and the other sets them after.
This script produces the following plot:


As you can see in Scenario 1, when
set_xlimis called on the empty axes, the view is fixed to a default [0, 1] range, and the subsequently plotted data (from x=2 to x=10) is not visible. This confirms the issue perfectly.I'd be happy to try and investigate a solution. Do you have any pointers on where in the codebase one might start to implement the "sticky" None behavior for autoscaling?
Thanks again for all your help!