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

Skip to content

set_ylim not working with plt.axis('equal') #8093

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
fkbreitl opened this issue Feb 17, 2017 · 30 comments · Fixed by #15032
Closed

set_ylim not working with plt.axis('equal') #8093

fkbreitl opened this issue Feb 17, 2017 · 30 comments · Fixed by #15032
Milestone

Comments

@fkbreitl
Copy link

fkbreitl commented Feb 17, 2017

set_ylim has no effect when using plt.axis('equal'):

plt.plot((.1, .3))
plt.axis('equal')
plt.gca().set_ylim(.1, .2)
@dstansby
Copy link
Member

What Matplotlib version, Python version and Platform are you using? On Linux, python 3.5.2 and matplotlib master branch I get the figure below. Is that what you were expecting or not?

figure_1

@fkbreitl
Copy link
Author

Sorry I forgot to mention I am using matplotlib 2.0.0 and the last command has no effect in my case.
So the y-axis remains from 5 to 8.

@dstansby
Copy link
Member

Hmm, this is working fine for me on 2.0.0 using python 3.6, in a fresh conda environment. What version of python are you using and what OS are you running?

@WeatherGod
Copy link
Member

WeatherGod commented Feb 17, 2017 via email

@fkbreitl
Copy link
Author

fkbreitl commented Feb 17, 2017

Sorry I didn't provide this information. I see it with Python 2.7.13 using IPython 5.1.0.
When using plt.savefig I obtain the correct result mentioned by you above.

However, my example was not well chosen. The actual problem appears in this example (also when using plt.savefig without IPython):

plt.plot((.1, .3))
plt.axis('equal')
plt.gca().set_ylim(.1, .2)

set_ylim

However, maybe this is in fact not a bug but a feature, since this behavior guaranties a fixed aspect ratio of plots, which could be handy when generating multiple plots. I was just surprised that the axes can be longer than the given limits. But it can be a good thing that matplotlib sees them as lower limits with respect to the other axis. Maybe the current behavior is not a bad idea and no changes are actually required.

@dstansby
Copy link
Member

Yep, I can confirm that example doesn't seem to be working.

@anntzer
Copy link
Contributor

anntzer commented Feb 18, 2017

@tacaswell
Copy link
Member

@fkbreitl Just to check, you edited the orginal post? I can not see a difference between the two examples...

I think the fundamental problem is that with a fixed aspect ratio the system is over constrained so we drop one of the constraints. The logic for all of this lives in apply_aspect, appears to have some special-casing for log scales, and would definitely benefit from better documentation!

@fkbreitl
Copy link
Author

@tacaswell The difference is that the first example respects the limits and the second doesn't.

If you say apply_aspect can help, then it would in fact be very useful to know how. The documentation is probably a very good place for that.

@tacaswell
Copy link
Member

tacaswell commented Feb 20, 2017

But I don't see how the examples are different, they are both

plt.plot((.1, .3))
plt.axis('equal')
plt.gca().set_ylim(.1, .2)

I suggest having a look at the source.

@tacaswell tacaswell added this to the 2.0.1 (next bug fix release) milestone Feb 20, 2017
@fkbreitl
Copy link
Author

@tacaswell Oh, you are right. It seems like dstansby edited my example.
I think my original example was something like

plt.plot((1,9))
plt.axis('equal')
plt.gca().set_ylim(5, 7)

@beuse
Copy link

beuse commented Apr 4, 2017

I found this page searching for a solution to the same problem. Here is a code snippet:

for angle in range(0,180+step,step):
    .
    fig = plt.figure('Angle {0}'.format(angle))
    plt.plot(x,y,'-')
    # plt.gca().set_adjustable('box') # this didn't help
    plt.grid()
    plt.axis('equal')  # if this is removed, then plt.axis works
    plt.axis((-8.0,8.0,-8.0,8.0))
plt.show()

The x-axis was correct, but the y-axis autoscaled. Interestingly, if I tried to manually scale the y=axis (using the check mark on the displayed plot) the values would not be accepted when I pushed apply. Python 3.5.2, Matplotlib 1.5.3

@tacaswell
Copy link
Member

@beuse You want ax.axis('square') I think.

@beuse
Copy link

beuse commented Apr 6, 2017

@tacaswell Yes you thought correctly. Thanks.

@QuLogic QuLogic modified the milestones: 2.0.1 (next bug fix release), 2.0.2 (next bug fix release) May 3, 2017
@tacaswell tacaswell modified the milestones: 2.1.1 (next bug fix release), 2.2 (next feature release) Oct 9, 2017
@ankit026
Copy link

try using plt.gca().set_aspect('equal')

@ghost
Copy link

ghost commented May 21, 2018

@tacaswell Thank you a billion!!!! That's exactly what I was looking for! All the other answers don't work!

@aggna
Copy link

aggna commented Jun 27, 2019

I am still having this problem. Using Python 3.6.8 in Notebook 5.7.8 with matplotlib 2.2.2
I don't need my axes box to be a square, but I do need x and y axes to be of the same scale, so that plotted objects dont get distorted.

Including two instances, one with this code as is, other with commenting out the lines between #Make axes be equally scaled and sp1.legend()
test_figure_equal_scaling
test_figure_no_scaling

    import matplotlib.pyplot as plt
    import numpy as np
    fig, [sp1,sp2] = plt.subplots(nrows=2, ncols=1, sharex='col' 
                                    ,gridspec_kw={'height_ratios': [3.5, 1]}
#                                   ,figsize = (7,8)
                              )
#     fig, [sp1,sp2] = plt.subplots(nrows=2, ncols=1, sharex='all'   
#     sp2 = plt.subplot2grid((3,1), (0,0),  rowspan=2)



#    These numbers are subject to change. 
#    Just picking an example for a standalone plotting script to 
#    provide MWE for GitHub. 
    rtr_dims = {'outer_radius':19,'inner_radius':15,'hole_radius':2.5,'bar_width':7,'height':10}
    sensors  = ({'coordinate':'cartesian','location':(17,0,11)},
               {'coordinate':'cartesian','location':(-28,0,0)})
    sources  = ({'coordinate':'cartesian','location':(17,0,0)},
               {'coordinate':'cartesian','location':(3,16,4)})

    th = np.arange(0,2*np.pi,0.01)
    rotor_out_x  = rtr_dims['outer_radius']*np.cos(th)
    rotor_out_y  = rtr_dims['outer_radius']*np.sin(th)
    rotor_in_x   = rtr_dims['inner_radius']*np.cos(th)
    rotor_in_y   = rtr_dims['inner_radius']*np.sin(th)
    rotor_hole_x = rtr_dims['hole_radius']*np.cos(th)
    rotor_hole_y = rtr_dims['hole_radius']*np.sin(th)
    
    rtr_color = 'magenta'

    h = sp1.plot(rotor_out_x,rotor_out_y,label = 'rotor boundary',color = rtr_color)
    sp1.plot(rotor_in_x,rotor_in_y,color = h[-1].get_color())
    sp1.plot(rotor_hole_x,rotor_hole_y,color = h[-1].get_color())
    bar_x = np.arange(-rtr_dims['inner_radius'],rtr_dims['inner_radius'],1)
    bar_y = rtr_dims['bar_width']/2*np.ones(len(bar_x))
    sp1.plot(bar_x,bar_y,bar_x,-bar_y,color = h[-1].get_color())

    sp1.set_ylabel('y (mm)')
    sp2.set_xlabel('x (mm)')
        
#     sp2 =  plt.subplot2grid((3,1), (2,0),sharex=sp1)
    rotor_out_z = rtr_dims['height']/2*np.ones(len(rotor_out_x))
    sp2.plot(rotor_out_x,rotor_out_z,rotor_out_x,-rotor_out_z,color = rtr_color)
    sp2.set_ylabel('z (mm)')
    
    vert_z = np.arange(-rtr_dims['height']/2,rtr_dims['height']/2,rtr_dims['height']/20)
    vert_x = rtr_dims['outer_radius']*np.ones(len(vert_z))
    sp2.plot(vert_x,vert_z,-vert_x,vert_z,color = rtr_color)
    
#     plt.xlabel('x')
    
    for i_sensor in range(len(sensors)):
        sensor_cartesian = sensors[i_sensor]
        hsens = sp1.scatter(sensor_cartesian['location'][0],sensor_cartesian['location'][1],label = 'sensor '+str(i_sensor+1))
        sp2.scatter(sensor_cartesian['location'][0],sensor_cartesian['location'][2],color = hsens.get_facecolor())
        
    for i_source in range(len(sources)):
        source_cartesian = sources[i_source]
        hsrc = sp1.scatter(source_cartesian['location'][0],source_cartesian['location'][1],label = 'source ' + str(i_source+1))
        sp2.scatter(source_cartesian['location'][0],source_cartesian['location'][2],color = hsrc.get_facecolor())
        
        
#     sp1.axis('scaled')
#     sp2.axis('scaled')
    xlim_sp1 = sp1.get_xlim()
    xlim_sp2 = sp2.get_xlim()
    ylim_sp1 = sp1.get_ylim()
    ylim_sp2 = sp1.get_ylim()
    
    # Make axes be equally scaled
    sp1.axis('equal')
    sp2.axis('equal')
    
    sp1.set_ylim(ylim_sp1)
    sp2.set_ylim(ylim_sp2)

    
    sp1.legend()
    

fig.savefig('test_figure_equal_scaling.png',bbox_inches = 'tight')

@timhoffm
Copy link
Member

@aggna I can't follow your example. Please boil it down to a minimal example if this is still a problem for you. Please also note that after axis('equal') explicit data limits may not be respected anymore to fulfill the equal scaling constraint.

@QuLogic QuLogic modified the milestones: needs sorting, v3.2.0 Aug 12, 2019
@aggna
Copy link

aggna commented Aug 19, 2019

@timhoffm @tacaswell hopefully this one is more demonstrative of the problem.
Anyways, I still couldn't find a proper automated fix for my problem, but managed to manually make everything correct.

Case 1

If I don't have two subplots, axis('equal') works fine.
Case_1

Case 2

The automated code fails to work when you have two subplots. For some reason, it fixes a figure size (even if you don't give it one) and axis('equal') only works to change axis limits, hence cropping the plot.
Case_2

Case 3

Manually chosing the figure size and subplot sizes first, depending on the data that is being plotted.
Case_3

Case 4

Manually chosing the figure size and subplot sizes first, depending on the data that is being plotted. Then use axis('equal') to take care of finetuning in guessed size.
Case_4

Attaching code.

Set up - no plotting yet

import matplotlib.pyplot as plt
import numpy as np

# Quantities used in all cases:
radius = 15
height = 10
dots   = np.array([[17,0,11],
              [-20,0,0],
              [17,3,0],
              [3,16,4]])

theta = np.arange(0,2*np.pi,0.01)
rotor_x  = radius*np.cos(theta)
rotor_y  = radius*np.sin(theta)
rotor_z = height/2*np.ones(len(rotor_x))
vert_z = np.arange(-height/2,height/2,height/20)
vert_x = radius*np.ones(len(vert_z))
rotor_color = 'magenta'


# Quantities used in Case 3 and 4:

max_y = max(radius,max((dots[:,1])))
min_y = min(-radius,min(dots[:,1]))
max_x = max(radius,max((dots[:,0])))
min_x = min(-radius,min(dots[:,0]))
max_z = max(height/2,max((dots[:,2])))
min_z = min(-height/2,min((dots[:,2])))
    
y_size = (max_y - min_y)
z_size = (max_z - min_z)
x_size = (max_x - min_x)

total_height = 0.1*(y_size + z_size ) + 1
total_width = 0.1*(x_size)

Case 1 code

## Case 1

sp1 = plt.axes()

# Make a circle
h = sp1.plot(rotor_x,rotor_y,color = rotor_color)
sp1.set_ylabel('y (mm)')
# Make some dots
for i in range(len(dots)):
    sensor_cartesian = dots[i]
    hsens = sp1.scatter(sensor_cartesian[0],sensor_cartesian[1])
sp1.axis('equal')
sp1.text(0,0,'Case 1')
    
plt.savefig('Case_1.png',bbox_inches='tight')

Case 2/3/4 code

## Case 2/3/4:

case =4

if case==2:
    # For case 2, let axis equal do the job
    fig, [sp1,sp2] = plt.subplots(nrows=2, ncols=1, sharex='col' 
                           ,gridspec_kw={'height_ratios': [3, 1]})
elif (case==3 or case==4):
    # For case 3 and 4, manually chose the figure size and subplot size
    fig, [sp1,sp2] = plt.subplots(nrows=2, ncols=1, sharex='col' 
                                    ,gridspec_kw={'height_ratios': [y_size/z_size, 1]}
                                  ,figsize = (total_width,total_height)
                              )
# Make axis equal for case 2 or 4:
if (case==2 or case==4):
    sp1.axis('equal')
    sp2.axis('equal')

# Make a circle
h = sp1.plot(rotor_x,rotor_y,color = rotor_color)
sp1.set_ylabel('y (mm)')
sp2.set_xlabel('x (mm)')
        
# Make the corresponding rectangle
sp2.plot(rotor_x,rotor_z,rotor_x,-rotor_z,color = rotor_color)
sp2.plot(vert_x,vert_z,-vert_x,vert_z,color = rotor_color)
sp2.set_ylabel('z (mm)')

# Plot all the dots
for i in range(len(dots)):
    sensor_cartesian = dots[i]
    hsens = sp1.scatter(sensor_cartesian[0],sensor_cartesian[1])
    sp2.scatter(sensor_cartesian[0],sensor_cartesian[2],color = hsens.get_facecolor())

       
sp1.text(0,0,'Case %d'%case)
fig.savefig('Case_%d.png'%case,bbox_inches = 'tight')

@timhoffm
Copy link
Member

@aggna This is correct and documented behavior.

  • A figure is always created with a fixed size. The figure may be created implicitly if necessary, when using pyplot. If not explicit size is given, it is taken from plt.rcParams['figure.figsize'].
  • axis('equal') scales the data limits to get the aspect right. See https://matplotlib.org/devdocs/api/_as_gen/matplotlib.pyplot.axis.html.
  • Alternatively, you may use axis('scale')) to resize the axes instead.
  • There is no way of autoscaling a figure to fix the aspect. The figure size is always fixed.

@jklymak
Copy link
Member

jklymak commented Aug 22, 2019

Printing a figure w/ bbox_inches='tight' will resize the figure. And a lot of people do that implicitly when they use %matplotlib inline in ipython/jupyter notebooks. I think its a mistake for the inline backend to do that, because it leads to lots of confusion, but...

@ImportanceOfBeingErnest
Copy link
Member

ImportanceOfBeingErnest commented Aug 23, 2019

I think I understand the issue. Minimal example:

import matplotlib.pyplot as plt
import numpy as np

x = np.array([-20, -3, 0, 17])
y = np.array([0, -16, 16, 0])

fig, (ax1,ax2) = plt.subplots(nrows=2, ncols=1, sharex='col', 
                              gridspec_kw={'height_ratios': [3, 1]})

ax1.set_aspect('equal', adjustable="datalim", share=True)
ax2.set_aspect('equal', adjustable="datalim", share=True)

h1, = ax1.plot(x, y)
h2, = ax2.plot(x, y / 4)

image

The problem: The lines are cut off. Now unlike other similar issues, which arise because the system is overconstrained, this is not the case here. The only constraints here are the equal aspect for both axes, and the sharing between the x axes. This would in principle still allow to adjust the x axis limits, such that the whole line is shown.

So the expected outcome would be this:

image

@jklymak
Copy link
Member

jklymak commented Aug 23, 2019

Yeah theoretically. But you’d need a constraint solver to figure it out. I’d say expecting sharing,autoscaling, and maintaining aspect across a number of axes is a pretty hard problem and you can either set up a big machinery to solve it or the user need not rely on autoscaling.

@aggna
Copy link

aggna commented Aug 23, 2019

@ImportanceOfBeingErnest Yes, that's what I was thinking axis('equal') would do. But alas!
@timhoffm Thanks for clarifying the expected behaviour.

@aggna
Copy link

aggna commented Aug 23, 2019

@jklymak The constraints aren't that complicated to solve, IMO. Just a bit of subtraction and division, as you can see from my O[10] lines of code in the second block in setup.

All plotting algorithms probably have the code structure that takes data range and uses those to implement the default axes limit. So now all they gotta do is take the grid-spec and figsize into account to decide the new axes limits.

@jklymak
Copy link
Member

jklymak commented Aug 23, 2019

So now all they gotta do is take the grid-spec and figsize into account to decide the new axes limits.

Sounds easy!

@aggna
Copy link

aggna commented Aug 23, 2019

@timhoffm Did you mean axis('scaled') ?
I tried it, but that one gave much more ridiculous results, so I abandoned it early on.

For folks who might encounter this problem later at some point, here's what you get if you replace axis('equal') by axis('scaled') in the above code, keeping everything else the same (I had to adjust the text location, but that's not the main point):
Case_1_scaled
Case_2_scaled
Case_3_scaled
Case_4_scaled

@ImportanceOfBeingErnest
Copy link
Member

Let's not confuse things here. The figure size is fixed (bbox_inches="tight" does not change the figure size, it just renders on a larger/smaller pixel space). Anything that would change the figure size is on a totally different level. The issue isolated above is about the datalimits. Possibly this is hard, too, but I still need to convince myself of that.

@jklymak
Copy link
Member

jklymak commented Aug 23, 2019

The figure size is fixed (bbox_inches="tight" does not change the figure size, it just renders on a larger/smaller pixel space).

It renders a different figure size in inches, but if you mean it doesn't change the figure size parameter for calculation of the axes positions, I agree with that. (I wouldn't think in pixels, since that does often change on a savefig, and is irrelevant for many of the backends).

@Victor-Victorovich
Copy link

try using plt.gca().set_aspect('equal')

This worked for me. I had the same issue as OP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.