Aspect ratio in subplots with various y-axes

匿名 (未验证) 提交于 2019-12-03 01:54:01

问题:

I would like the following code to produce 4 subplots of the same size with a common aspect ratio between the size of x-axis and y-axis set by me. Referring to the below example, I would like all of the subplots look exactly like the first one (upper left). What is wrong right now is that the size of the y-axis is correlated with its largest value. That is the behaviour I want to avoid.

import matplotlib.pyplot as plt import numpy as np  def main():       fig = plt.figure(1, [5.5, 3])     for i in range(1,5):         fig.add_subplot(221+i-1, adjustable='box', aspect=1)          plt.plot(np.arange(0,(i)*4,i))      plt.show()  if __name__ == "__main__":      main() 

Surprisingly, matplotlib produces the right thing by default (picture below):

   import  matplotlib.pyplot as plt     import numpy as np      def main():         fig = plt.figure(1, [5.5, 3])         for i in range(1,5):             fig.add_subplot(221+i-1)              plt.plot(np.arange(0,(i)*4,i))         plt.show()  

I just want to add to this an ability to control the aspect ratio between lengths of x and y-axes.

回答1:

I can't quite tell what you want from your question.

Do you want all of the plots to have the same data limits?

If so, use shared axes (I'm using subplots here, but you can avoid it if you want to stick to matlab-style code):

import matplotlib.pyplot as plt import numpy as np  fig, axes = plt.subplots(nrows=2, ncols=2, sharey=True, sharex=True) for i, ax in enumerate(axes.flat, start=1):     ax.set(aspect=1)     ax.plot(np.arange(0, i * 4, i))  plt.show() 

If you want them all to share their axes limits, but to have adjustable='box' (i.e. non-square axes boundaries), use adjustable='box-forced':

import matplotlib.pyplot as plt import numpy as np  fig, axes = plt.subplots(nrows=2, ncols=2, sharey=True, sharex=True) for i, ax in enumerate(axes.flat, start=1):     ax.set(aspect=1, adjustable='box-forced', xticks=range(i))     ax.plot(np.arange(0, i * 4, i))  plt.show() 


Edit: Sorry, I'm still a bit confused. Do you want something like this?

import matplotlib.pyplot as plt  import numpy as np   fig, axes = plt.subplots(nrows=2, ncols=2) for i, ax in enumerate(axes.flat, start=1):     ax.set(adjustable='datalim', aspect=1)     ax.plot(np.arange(0, i * 4, i))  plt.show() 


Okay, I think I finally understand your question. We both meant entirely different things by "aspect ratio".

In matplotlib, the aspect ratio of the plot refers to the relative scales of the data limits. In other words, if the aspect ratio of the plot is 1, a line with a slope of one will appear at 45 degrees. You were assuming that the aspect ratio applied to the outline of the axes and not the data plotted on the axes.

You just want the outline of the subplots to be square. (In which case, they all have different aspect ratios, as defined by matplotlib.)

In that case, you need a square figure. (There are other ways, but just making a square figure is far simpler. Matplotlib axes fill up a space that is proportional to the size of the figure they're in.)

import matplotlib.pyplot as plt  import numpy as np   # The key here is the figsize (it needs to be square). The position and size of # axes in matplotlib are defined relative to the size of the figure. fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8,8))  for i, ax in enumerate(axes.flat, start=1):     ax.plot(np.arange(0, i * 4, i))  # By default, subplots leave a bit of room for tick labels on the left. # We'll remove it so that the axes are perfectly square. fig.subplots_adjust(left=0.1)  plt.show() 



回答2:

Combing the answer of Joe Kington with new pythonic style for shared axes square subplots in matplotlib? and another post that I am afraid I cannot find it again, I made a code for precisely setting the ratio of the box to a given value.

Let desired_box_ratioN indicate the desired ratio between y and x sides of the box. temp_inverse_axis_ratioN is the ratio between x and y sides of the current plot; since 'aspect' is the ratio between y and x scale (and not axes), we need to set aspect to desired_box_ratioN * temp_inverse_axis_ratioN.

import matplotlib.pyplot as plt import numpy as np  fig, axes = plt.subplots(nrows=2, ncols=2)  desired_box_ratioN = 1 for i, ax in enumerate(axes.flat, start=1):     ax.plot(np.arange(0, i * 4, i))     temp_inverse_axis_ratioN = abs( (ax.get_xlim()[1] - ax.get_xlim()[0])/(ax.get_ylim()[1] - ax.get_ylim()[0]) )     ax.set(aspect = desired_box_ratioN * temp_inverse_axis_ratioN, adjustable='box-forced')  plt.show() 


回答3:

The theory

Different coordinate systems exists in matplotlib. The differences between different coordinate systems can really confuse a lot of people. What the OP want is aspect ratio in display coordinate but ax.set_aspect() is setting the aspect ratio in data coordinate. Their relationship can be formulated as:

aspect = 1.0/dataRatio*dispRatio 

where, aspect is the argument to use in set_aspect method, dataRatio is aspect ratio in data coordinate and dispRatio is your desired aspect ratio in display coordinate.

The practice

There is a get_data_ratio method which we can use to make our code more concise. A work code snippet is shown below:

import matplotlib.pyplot as plt import numpy as np  fig, axes = plt.subplots(nrows=2, ncols=2)  dispRatio = 0.5 for i, ax in enumerate(axes.flat, start=1):     ax.plot(np.arange(0, i * 4, i))     ax.set(aspect=1.0/ax.get_data_ratio()*dispRatio, adjustable='box-forced')  plt.show() 

I have also written a detailed post about all this stuff here.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!