Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Surprising/changed axis limit (autoscale) behavior #17331

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mwaskom opened this issue May 5, 2020 · 12 comments · Fixed by #17408
Closed

Surprising/changed axis limit (autoscale) behavior #17331

mwaskom opened this issue May 5, 2020 · 12 comments · Fixed by #17408
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
Milestone

Comments

@mwaskom
Copy link

mwaskom commented May 5, 2020

Bug report

Bug summary

The interaction of setting axis limits and autoscaling changed in 3.2 with little guidance, introducing unexpected behavior that doesn't always make sense.

Code for reproduction

Here is a plot that looks very different on 3.1.2 and 3.2.1

import numpy as np
import matplotlib.pyplot as plt
f, ax = plt.subplots()
x = np.arange(100)
y = np.random.uniform(-.1, .1, 100)
ax.scatter(x, y)
assert ax._autoscaleYon
ax.set_ylim((-.5, .5), auto=None)

Actual outcome

On 3.2.1

image

Expected outcome

On 3.1.2

image

Matplotlib version

  • Operating system: macOS
  • Matplotlib version: 3.2.1
  • Matplotlib backend (print(matplotlib.get_backend())): pylab inline
  • Python version: 3.7
@mwaskom
Copy link
Author

mwaskom commented May 5, 2020

Is "autoscaling" actually documented anywhere? https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.autoscale.html just says "turn autoscaling on or off" but that's a bit tautological: it's saying "function f does f". What effect does f have?

@tacaswell tacaswell added this to the v3.2.2 milestone May 5, 2020
@tacaswell tacaswell added the Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. label May 5, 2020
@jklymak
Copy link
Member

jklymak commented May 5, 2020

Bisects to 160de56 #13593 @anntzer any quick idea what's going on here?

I didn't even know about the auto=None option.

@tacaswell
Copy link
Member

https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.autoscale_view.html is the method that actually does the rescaling and has a slightly better explaination. This also gets coupled with "margins" and "sticky edges" https://matplotlib.org/gallery/subplots_axes_and_figures/axes_margins.html.

I suspect that this related to making the auto-scaling lazier.

On master I get a flash of the wrong scaling before it goes to the correct limits but it is still broken an v3.2.x.

I suspect the problem is that adding the scatter "arms" the rescale, but setting the ylim does not "disarm" it. If you force a f.canvas.draw() just before setting the ylim it works correctly.

@jklymak
Copy link
Member

jklymak commented May 5, 2020

This only happens w/ auto=None, which says to keep the autoscaling as it was before the set_ylim. So I'd argue the plot is correct since you are asking for the y-axis to be autoscaled, at least according to the docs.

What did auto=None do before? Only autoscale only if more data is added? Thats super confusing in my opinion, but maybe seaborn has a use case?

@mwaskom
Copy link
Author

mwaskom commented May 5, 2020

So I'd argue the plot is correct since you are asking for the y-axis to be autoscaled, at least according to the docs.

No, I'm asking for the y axis limits to be (-.5, +.5) at this moment in time. I am also asking for this action not to change whether or not the plot will continue to autoscale in the future.

What did auto=None do before? Only autoscale only if more data is added?

Yes. My understanding, and the previous behavior, is that the auto parameter of set_{x,y}lim influences what happens in the future, not in response to that call.

Thats super confusing in my opinion, but maybe seaborn has a use case?

Why is that super confusing? If I am calling a method to set explicit limits, I don't think matplotlib should ignore my limits and autoscale to narrower values.

@mwaskom
Copy link
Author

mwaskom commented May 5, 2020

To be clear, there has been a change when auto=True as well.

import numpy as np
import matplotlib.pyplot as plt
f, ax = plt.subplots()
x = np.arange(20)
y = np.linspace(-.1, .1, 20)
ax.scatter(x, y)
assert ax._autoscaleYon
ax.set_ylim((-.5, .5), auto=True)

3.1.2:

image

3.2.1:

image

@anntzer
Copy link
Contributor

anntzer commented May 5, 2020

I guess

diff --git i/lib/matplotlib/axes/_base.py w/lib/matplotlib/axes/_base.py
index 8f60cfedd..bd727a529 100644
--- i/lib/matplotlib/axes/_base.py
+++ w/lib/matplotlib/axes/_base.py
@@ -3331,6 +3331,9 @@ class _AxesBase(martist.Artist):
         left, right = sorted([left, right], reverse=bool(reverse))
 
         self._viewLim.intervalx = (left, right)
+        # Mark viewlims as no longer stale without triggering an autoscale.
+        for ax in self._shared_x_axes.get_siblings(self):
+            ax._stale_viewlim_x = False
         if auto is not None:
             self._autoscaleXon = bool(auto)
 
@@ -3600,6 +3603,9 @@ class _AxesBase(martist.Artist):
         bottom, top = sorted([bottom, top], reverse=bool(reverse))
 
         self._viewLim.intervaly = (bottom, top)
+        # Mark viewlims as no longer stale without triggering an autoscale.
+        for ax in self._shared_y_axes.get_siblings(self):
+            ax._stale_viewlim_y = False
         if auto is not None:
             self._autoscaleYon = bool(auto)

is what we need, can you confirm that works for you?

@jklymak
Copy link
Member

jklymak commented May 5, 2020

Why is that super confusing? If I am calling a method to set explicit limits, I don't think matplotlib should ignore my limits and autoscale to narrower values.

I agree, but then to autoscale if I add something new is surprising. I can see that its a useful feature, but it adds some complexity to the state of the plot that we have to keep track of, and obviously got dropped here.

@anntzer, your patch appears to work for me. But I guess we need to test this flag better.

@mwaskom
Copy link
Author

mwaskom commented May 5, 2020

I agree, but then to autoscale if I add something new is surprising. I can see that its a useful feature, but it adds some complexity to the state of the plot that we have to keep track of, and obviously got dropped here.

Well the reason seaborn uses auto=None is mostly for backwards compatibility, as it matches how ax.set{x,y}lim had previously worked. I recall the original change causing some pain that seemed best solved at the time by just preserving the old behavior, but I don't remember the details of the issues.

@mwaskom
Copy link
Author

mwaskom commented May 5, 2020

What I really want from explicit limits / autoscaling is something closer to a ratchet: I want to set limits that won't hide to-be-added data that lie outside of the limits but will also not contract when to-be-added data lie inside the limits (minus the margin).

But I'd settle for some better/less tautological documentation for how autoscale is supposed to work :)

@ianhi
Copy link
Contributor

ianhi commented Aug 20, 2020

What I really want from explicit limits / autoscaling is something closer to a ratchet:

@mwaskom I agree, that would be great. Until such a dream is realized I found that I could get satisfactory results like so:

# for ylims only
cur_ylims = ax.get_ylim()
ax.relim()
new_lims = [ax.dataLim.y0, ax.dataLim.y0+ax.dataLim.height]
new_lims = [
        new_lims[0] if new_lims[0]<cur_ylims[0] else cur_ylims[0],
        new_lims[1] if new_lims[1]>cur_ylims[1] else cur_ylims[1]
        ]
ax.set_ylim(new_lims)

Full code + associated x_lim version lives here:
https://github.com/ianhi/mpl-interactions/blob/a5cd394514418215ed61462c5c35d33e312f52cd/mpl_interactions/jupyter.py#L64-L70

@mwaskom
Copy link
Author

mwaskom commented Aug 20, 2020

@ianhi cleaner to write that code as

ax.set_ylim(min(new_lims[0], cur_ylims[0]), max(new_lims[1], cur_ylims[1]))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants