Skip to content

Surprising/changed axis limit (autoscale) behavior #17331

Closed

Description

@mwaskom

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

Activity

  1. mwaskom commented on May 5, 2020

    @mwaskom
    Author

    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?

  2. added this to the v3.2.2 milestone on May 5, 2020
  3. added
    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.
    on May 5, 2020
  4. jklymak commented on May 5, 2020

    @jklymak
    Member

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

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

  5. tacaswell commented on May 5, 2020

    @tacaswell
    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.

  6. jklymak commented on May 5, 2020

    @jklymak
    Member

    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?

  7. mwaskom commented on May 5, 2020

    @mwaskom
    Author

    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.

  8. mwaskom commented on May 5, 2020

    @mwaskom
    Author

    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

  9. anntzer commented on May 5, 2020

    @anntzer
    Contributor

    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?

  10. jklymak commented on May 5, 2020

    @jklymak
    Member

    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.

  11. mwaskom commented on May 5, 2020

    @mwaskom
    Author

    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.

  12. mwaskom commented on May 5, 2020

    @mwaskom
    Author

    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 :)

  13. added 2 commits that reference this issue on May 14, 2020
    15748b2
    2f41868
  14. ianhi commented on Aug 20, 2020

    @ianhi
    Contributor

    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

  15. mwaskom commented on Aug 20, 2020

    @mwaskom
    Author

    @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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Release criticalFor bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions.

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions