简体   繁体   中英

Get true limit of plot area of an Axes in matplotlib?

I'd like to find out the exact extents of the data inside a matplotlib Axes object; ie, their highest and lowest x-values, and highest and lowest y-values. (My specific application only needs me to be able to determine the highest x-value with an error of less than 0.5, but I'm curious about the general case.)

You're supposed to use Axes.get_xlim() and .get_ylim() to get the extent of an Axes object, but due to the margins, these limits will be slightly outside the data limits. For example:

import matplotlib.pyplot as plt

plt.plot(range(100), range(100))
ax = plt.gca()
print(ax.get_xlim()) # prints (-4.95, 103.95)

The margins are given by Axes.margins() as a fraction of the axes, so what if we subtract those? We end up underestimating the limits a little:

xlim, xmargin = ax.get_xlim(), ax.margins()[0]
width = xlim[1] - xlim[0]
lower_xlim = xlim[0] + width*xmargin # 0.495, should be 0
upper_xlim = xlim[1] - width*xmargin # 98.505, should be 99

With trial and error, we find that if we multiply the correcting term by 10/11 , we finally hit our mark:

lower_xlim = xlim[0] + width*xmargin*10/11 # 0.
upper_xlim = xlim[1] - width*xmargin*10/11 # 99.

Upon closer inspection with different data ranges and margin sizes, it turns out that the actual factor is:

lower_xlim = xlim[0] + width*5/110 # 0.
upper_xlim = xlim[1] - width*5/110 # 99.

Very bizarre, but it works very well. Except... what if we're using Axes.imshow() instead of Axes.plot() ? The plot doesn't have margins in that case (if Axes.use_sticky_edges() is True , which is the default), but the result of Axes.margins() is the same either way. Also, this might be messed up by the addition of non-data Artists such as text boxes outside of the current bounds. (Maybe. I've never tried it.)

I know that a foolproof way to find the data limits would be to iterate through all of the data in the Axes object and compute their maxima and minima. This can be done by finding out the Axes object's artists with Axes.get_children() , and grabbing the data from those that provide the method .get_data() . But this is very computationally expensive.

Is there a better way? Or is my 5/110 hack the next best thing? Why is the 5/110 factor even required? Why doesn't it depend on xmargin ? (Should I call Axes.autoscale() first?)

this should work properly:

lower_xlim = xlim[0] + (0.5 * xmargin) / (0.5 + xmargin) * width
upper_xlim = xlim[1] - (0.5 * xmargin) / (0.5 + xmargin) * width

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM