10

I'm trying to draw on an existing axis without extending or modifying its limits.

For example:

import numpy as np
import matplotlib.pyplot as plt

xy = np.random.randn(100, 2)

plt.scatter(xy[:,0], xy[:,1])

Makes a fine plot with well-fitting axis limits.

However, when I try to draw a line on top of it:

xlim = plt.gca().get_xlim()
plt.plot(xlim, xlim, 'k--')

the axis limits are extended, presumably to create padding around the new data.

How can I draw a line without this padding?

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
shadowtalker
  • 12,529
  • 3
  • 53
  • 96

4 Answers4

8

Setting plt.autoscale(False) prevents autoscaling from happening.

import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt

xy = np.random.randn(100, 2)
# By default plots are autoscaled. 
plt.scatter(xy[:,0], xy[:,1])

#Turn autoscaling off
plt.autoscale(False)
xlim = plt.gca().get_xlim()
plt.plot(xlim, xlim, 'k--')

plt.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • This is the answer. Unfortunately `tight=True` appears to be "retroactive", messing up the scaling of the data that has already been plotted. Whereas `enabled=False` does not appear to undo the initial autoscaling. – shadowtalker Oct 10 '18 at 16:26
  • No, autoscaling is not retroactive. It will concern everything that is plotted after setting it. – ImportanceOfBeingErnest Oct 10 '18 at 16:33
  • In that case there appears to be a bug in matplotlib. Look what happens if you write `plt.autoscale(tight=True)` instead of `plt.autoscale(False)` in your code. It actually changes the scaling around the scatter points. – shadowtalker Oct 10 '18 at 16:46
  • Well, that's expected, right? Because `enable` is True by default. Maybe I'm not completely understanding what you are trying to achieve. – ImportanceOfBeingErnest Oct 10 '18 at 16:53
  • My point is that the behavior is inconsistent. One parameter is retroactive and the other is not. It appears that setting up initial scaling might be a special case. – shadowtalker Oct 10 '18 at 16:59
  • No, every parameter affects only what comes after the command. – ImportanceOfBeingErnest Oct 10 '18 at 17:03
  • Maybe the issue is just with how parameters are passed? E.g. consider `def func(a=5, b=5): return a*b` When calling `func(b=2)` the result is 10, even though you did not specify `a` explicitely. – ImportanceOfBeingErnest Oct 10 '18 at 17:12
  • Look at this: `plt.autoscale(True, tight=False); plt.scatter(xy[:,0], xy[:,1]); plt.autoscale(tight=True); plt.plot(xlim, xlim, 'k--')`. The scaling of the whole plot is altered. Compare to `plt.autoscale(True, tight=False); plt.scatter(xy[:,0], xy[:,1]); plt.autoscale(False); plt.plot(xlim, xlim, 'k--')`. – shadowtalker Oct 10 '18 at 17:15
  • `plt.autoscale(tight=True)` is identical to `plt.autoscale(enable=True, tight=True)`. Hence in the first case you keep autoscaling enabled, even for the second plot. In the second case, you turn autoscaling off before the second plot. Hence it does not change the scaling. – ImportanceOfBeingErnest Oct 10 '18 at 17:47
  • I see. So it's actually re-scaling the plot *down* to fit the line "tightly", thereby making it too tight around the points that were previously drawn. – shadowtalker Oct 10 '18 at 18:26
3

One brute-force solution is to keep track of the axis limits before drawing, and reset them after.

Like so:

from contextlib import contextmanager

@contextmanager
def preserve_limits(ax=None):
    """ Plot without modifying axis limits """
    if ax is None:
        ax = plt.gca()

    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    try:
        yield ax
    finally:
        ax.set_xlim(xlim)
        ax.set_ylim(ylim)

Now compare

plt.scatter(xy[:,0], xy[:,1])

xlim = plt.gca().get_xlim()
plt.plot(xlim, xlim, 'k--')

with

plt.scatter(xy[:,0], xy[:,1])

with preserve_limits():
    xlim = plt.gca().get_xlim()
    plt.plot(xlim, xlim, 'k--')
shadowtalker
  • 12,529
  • 3
  • 53
  • 96
2

You can use the autoscale property of Axes objects:

Per the documentation:

Axes.autoscale(enable=True, axis='both', tight=None)

Autoscale the axis view to the data (toggle).

Convenience method for simple axis view autoscaling. It turns autoscaling on or off, and then, if autoscaling for either axis is on, it performs the autoscaling on the specified axis or axes. Parameters:

enable : bool or None, optional
         True (default) turns autoscaling on, False turns it off. None leaves the autoscaling state unchanged.
axis : {'both', 'x', 'y'}, optional
       which axis to operate on; default is 'both'
tight: bool or None, optional
       If True, set view limits to data limits; if False, let the locator
       and margins expand the view limits; if None, use tight scaling
       if the only artist is an image, otherwise treat tight as False.
       The tight setting is retained for future autoscaling until it is
       explicitly changed.
fig, ax = plt.subplots()
ax.plot(np.random.normal(size=(100,)),np.random.normal(size=(100,)),'bo')
ax.autoscale(tight=True)
xlim = ax.get_xlim()
plt.plot(xlim, xlim, 'k--')

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75
  • Will setting `tight=True` "retroactively" tighten the axis scaling? I like the initial non-tight autoscaling, but I want the autoscaling to become tight only for drawing the line. – shadowtalker Oct 10 '18 at 15:46
  • It also looks like I can use `Axes.autoscale_view` here but I'm not sure – shadowtalker Oct 10 '18 at 15:48
  • 1
    I just checked. `tight=True` appears to be retroactive, whereas `enabled=False` does not. – shadowtalker Oct 10 '18 at 16:25
  • @shadowtalker setting only `tight=True` keeps the autoscaling enabled, and so on every new plot changes the limits to fit all data, while `enabled=False` disables scaling altogether, and so keeps the limits fixed. – hugovdberg May 10 '19 at 11:31
1

If you set the x-axis limits separately, they won't be overwritten until you change them, regardless of what is plotted. to make it mesh with your code, try:

plt.xlim(xlim)

when you get xlim, it gets the current limits, but once you 'set' them, they're locked until you change them again. This works for the y-axis as well, if you want those to be fixed too (just swap 'x' for 'y' and add the code).

Jgd10
  • 497
  • 2
  • 14