jarondl
jarondl

Reputation: 1663

Make matplotlib autoscaling ignore some of the plots

I use matplotib's Axes API to plot some figures. One of the lines I plot represents the theoretical expected line. It has no meaning outside of the original y and x limits. What I want, is for matlplotlib to ignore it when autoscaling the limits. What I used to do, is to check what are the current limits, then plot, and reset the limits. The problem is that when I plot a third plot, the limits get recalculated together with the theoretical line, and that really expands the graph.

# Boilerplate
from matplotlib.figure import Figure
from matplotlib.backends.backend_pdf import FigureCanvasPdf
from numpy import sin, linspace


fig = Figure()
ax = fig.add_subplot(1,1,1)

x1 = linspace(-1,1,100)
ax.plot(x1, sin(x1))
ax.plot(x1, 3*sin(x1))
# I wish matplotlib would not consider the second plot when rescaling
ax.plot(x1, sin(x1/2.0))
# But would consider the first and last

canvas_pdf = FigureCanvasPdf(fig)
canvas_pdf.print_figure("test.pdf")

Upvotes: 20

Views: 15136

Answers (5)

mshport
mshport

Reputation: 51

As a generalisation of jam's answer, a collection object can be obtained from any of matplotlib's plotting functions and then re-added with autolim=False. For example,

fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)

# Get hold of collection
collection = ax.plot(x1, np.sin(x1))

# Remove collection from the plot
collection.remove()

# Rescale
ax.relim()

# Add the collection without autoscaling
ax.add_collection(collection, autolim=False)

Upvotes: 4

golmschenk
golmschenk

Reputation: 12424

This can be done regardless of plotting order by creating another axes to work on.

In this version, we create a twin axes and disable the autoscaling on that twin axes. In this way, the plot is scaled based on anything plotted in the original axes, but is not scaled by anything put into the twin axes.

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
twin_ax = ax.twinx()  # Create a twin axes.
twin_ax.autoscale(False)  # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([])  # Remove the extra tick numbers from the twin axis.

ax.plot(x1, np.sin(x1))
twin_ax.plot(x1, 3 * np.sin(x1), c='green')  # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))

twin_ax.set_ylim(ax.get_ylim())  # Make sure the y limits of the twin matches the autoscaled of the original.

fig.savefig('test.pdf')

Not scaled twin axes

Note, the above only prevents the un-twined axis from auto scaling (y in the above case). To get it to work for both x and y, we can do the twinning process for both x and y (or create the new axes from scratch):

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)
x2 = np.linspace(-2,2,100)  # Would extend the x limits if auto scaled
twin_ax = ax.twinx().twiny()  # Create a twin axes.
twin_ax.autoscale(False)  # Turn off autoscaling on the twin axes.
twin_ax.set_yticks([])  # Remove the extra tick numbers from the twin axis.
twin_ax.set_xticks([])  # Remove the extra tick numbers from the twin axis.

ax.plot(x1, np.sin(x1))
twin_ax.plot(x2, 3 * np.sin(x2), c='green')  # Plotting the thing we don't want to scale on in the twin axes.
ax.plot(x1, np.sin(x1 / 2.0))

twin_ax.set_ylim(ax.get_ylim())  # Make sure the y limits of the twin matches the autoscaled of the original.
twin_ax.set_xlim(ax.get_xlim())  # Make sure the x limits of the twin matches the autoscaled of the original.

fig.savefig('test.png')

Upvotes: 4

jam
jam

Reputation: 131

LineCollection objects can be ignored by using the autolim=False argument:

from matplotlib.collections import LineCollection

fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)

# Will update limits
ax.plot(x1, np.sin(x1))

# Will not update limits
col = LineCollection([np.column_stack((x1, 3 * np.sin(x1)))], colors='g')
ax.add_collection(col, autolim=False)

# Will still update limits
ax.plot(x1, np.sin(x1 / 2.0))

See this image of the output

Upvotes: 7

Joe Kington
Joe Kington

Reputation: 284760

The obvious way is to just manually set the limits to what you want. (e.g. ax.axis([xmin, xmax, ymin, ymax]))

If you don't want to bother with finding out the limits manually, you have a couple of options...

As several people (tillsten, Yann, and Vorticity) have mentioned, if you can plot the function you want to ignore last, then you can disable autoscaling before plotting it or pass the scaley=False kwarg to plot

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
x1 = np.linspace(-1,1,100)

ax.plot(x1, np.sin(x1))
ax.plot(x1, np.sin(x1 / 2.0))
ax.autoscale(False)         #You could skip this line and use scalex=False on
ax.plot(x1, 3 * np.sin(x1)) #the "theoretical" plot. It has to be last either way

fig.savefig('test.pdf')

Note that you can adjust the zorder of the last plot so that it's drawn in the "middle", if you want control over that.

If you don't want to depend on the order, and you do want to just specify a list of lines to autoscale based on, then you could do something like this: (Note: This is a simplified version assuming you're dealing with Line2D objects, rather than matplotlib artists in general.)

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms

def main():
    fig, ax = plt.subplots()
    x1 = np.linspace(-1,1,100)

    line1, = ax.plot(x1, np.sin(x1))
    line2, = ax.plot(x1, 3 * np.sin(x1))
    line3, = ax.plot(x1, np.sin(x1 / 2.0))
    autoscale_based_on(ax, [line1, line3])

    plt.show()

def autoscale_based_on(ax, lines):
    ax.dataLim = mtransforms.Bbox.unit()
    for line in lines:
        xy = np.vstack(line.get_data()).T
        ax.dataLim.update_from_data_xy(xy, ignore=False)
    ax.autoscale_view()

if __name__ == '__main__':
    main()

enter image description here

Upvotes: 24

tillsten
tillsten

Reputation: 14878

Use the scalex/scaley kw arg:

plot(x1, 3*sin(x1), scaley=False)

Upvotes: 7

Related Questions