Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rescaling quiver arrows in physical units consistent to the aspect ratio of the axes

I've been spending the past couple of hours trying to customize matplotlib.quiver without much luck. The documentation is quite confusing and I haven't been able to parse out how to set each parameter. In my axes, one vertical pixel is much less physical distance than one horizontal pixel, and I want quiver to autoscale the arrows to this aspect ratio. What I'm currently getting are horizontal arrows with the following code:

Tlevs = np.arange(-1.,8.5,.5) + 0.
yy, zz = np.meshgrid(ds.YC, ds.Z)

fig, ax = plt.subplots(figsize=(8,5))
fig.set_tight_layout(True)
im = ax.contourf(T_clim.YC, T_clim.Z, T_clim, levels=Tlevs)
ax.quiver(yy[::3,::10], zz[::3,::10], 
          vpFep_b[::3,::10], wpFep_b[::3,::10], 
          pivot='mid', angles='xy', units='xy')
ax.set_xlabel('Y [m]', fontsize=13)
ax.set_ylabel('Depth [m]', fontsize=13)
cbar = fig.colorbar(im, ax=ax)
cbar.set_label(r"[$^\circ$C]")

Contour plot with quiver overlaying it

Setting the quiver options to the following only gives dots for what the arrows should be:

ax.quiver(yy[::3,::10], zz[::3,::10], 
              vpFep_b[::3,::10], wpFep_b[::3,::10], 
              pivot='mid', angles='xy', scale_units='xy', scales=1.)

enter image description here

and:

ax.quiver(yy[::3,::10], zz[::3,::10], 
              vpFep_b[::3,::10], wpFep_b[::3,::10], 
              pivot='mid', angles='xy', scale_units='xy')

gives the following error:

/home/takaya/miniconda3/envs/uptodate/lib/python3.6/site-packages/matplotlib/quiver.py:666: RuntimeWarning: divide by zero encountered in double_scalars
  length = a * (widthu_per_lenu / (self.scale * self.width))
/home/takaya/miniconda3/envs/uptodate/lib/python3.6/site-packages/matplotlib/quiver.py:666: RuntimeWarning: invalid value encountered in multiply
  length = a * (widthu_per_lenu / (self.scale * self.width))
/home/takaya/miniconda3/envs/uptodate/lib/python3.6/site-packages/matplotlib/quiver.py:719: RuntimeWarning: invalid value encountered in less
  short = np.repeat(length < minsh, 8, axis=1)
/home/takaya/miniconda3/envs/uptodate/lib/python3.6/site-packages/matplotlib/quiver.py:733: RuntimeWarning: invalid value encountered in less
  tooshort = length < self.minlength

Any help would be appreciated. Thanks!

like image 893
takachanbo Avatar asked Apr 17 '18 21:04

takachanbo


1 Answers

The quiver documentation says:

To plot vectors in the x-y plane, with u and v having the same units as x and y, use angles='xy', scale_units='xy', scale=1

Thus, using this arguments your problem will be solved. That is because the keyword units affects the arrow dimensions except for length, and scale_units affects only the lenght.

Edit:

Examples of parameter behaviour of matplotlib's quiver

It is true that the documentation is not completely clear, and there are many parameters with similar names which in addition to that, are unique to quiver.

Below there are many examples of the behaviour of different parameters, in particular the ones of interest to this question: angles, units and scale_units. Each example has an image that can be expanded by clicking on it.

The data for all the plots is the same, and can be reproduced using this code:

x = np.linspace(0,50,5)
y = np.linspace(-150,150,7)
X,Y = np.meshgrid(x,y)
U = 3.5*np.ones_like(X)
V = 3.5*np.ones_like(Y)

angles

The entry in the documenatation is:

angles : [ ‘uv’ | ‘xy’ ], array, optional

Method for determining the angle of the arrows. Default is ‘uv’.

‘uv’: the arrow axis aspect ratio is 1 so that if U*==*V the orientation of the arrow on the plot is 45 degrees counter-clockwise from the horizontal axis (positive to the right).

‘xy’: arrows point from (x,y) to (x+u, y+v). Use this for plotting a gradient field, for example.

In our case, the ratio between U and V is one, therefore the arrows will point in 45 degrees in the 'uv' case, however, in the 'xy' case, as the range of the y and x axis is different, the arrows won't point in 45 degrees in order to preserve the displacement from (x,y) to (x+u, y+v). This is important to take into account, since depending on the axis aspect ratio, a gradient (u,v)=(1,1) won't have a 45 degrees angle.

angles

units

The entry of the documentation is:

units : [ ‘width’ | ‘height’ | ‘dots’ | ‘inches’ | ‘x’ | ‘y’ | ‘xy’ ]

The arrow dimensions (except for length) are measured in multiples of this unit.

‘width’ or ‘height’: the width or height of the axis

‘dots’ or ‘inches’: pixels or inches, based on the figure dpi

‘x’, ‘y’, or ‘xy’: respectively X, Y, or in data units

The arrows scale differently depending on the units. For ‘x’ or ‘y’, the arrows get larger as one zooms in; for other units, the arrow size is independent of the zoom state. For ‘width or ‘height’, the arrow size increases with the width and height of the axes, respectively, when the window is resized; for ‘dots’ or ‘inches’, resizing does not change the arrows.

As explained above, this parameters defines the units in which all dimensions except the length of the arrow are measured. However, the parameter width (which is the most rellevant since the arrow headwidth and headlength and so on are defined as multiples of it) has a default values which depends on what units are used.

Below there is an example of different units, fixing the width to comparable cases. The parameter dots or inches is nearly equivalent, and therefore only one of them is considered in the example. Thus, width is set to 0.01 for 'width' and 'height', 3 for 'dots' and 2 for 'x','y' and 'xy'. Not fixing the same width yields different result depending on the backend: i.e. with matplotlib inline in jupyter there is no difference in any case and with Qt5 there are some, but its hard to interpret as the width is unknown.

units

It can be seen that as the plot axes are wider than taller, setting units to 'width' yields thicker arrows compared to 'length', because the width is the same. The same goes when comparing 'x', 'y' and 'xy', a distance of 1 measured according to the x axis is much larger than a distance of 1 according to the y axis.

scale_units

The entry in the documentation is:

scale_units : [ ‘width’ | ‘height’ | ‘dots’ | ‘inches’ | ‘x’ | ‘y’ | ‘xy’ ], None, optional

If the scale kwarg is None, the arrow length unit. Default is None.

e.g. scale_units is ‘inches’, scale is 2.0, and (u,v) = (1,0), then the vector will be 0.5 inches long.

If scale_units is ‘width’/’height’, then the vector will be half the width/height of the axes.

If scale_units is ‘x’ then the vector will be 0.5 x-axis units. To plot vectors in the x-y plane, with u and v having the same units as x and y, use angles='xy', scale_units='xy', scale=1.

The explanation about scaling when resizing the plot is the same as in units. However, the "If the scale kwarg is None" statment is completely unclear and leads to error.

If scale is None, then the lenght of the arrows will be set to a default value depending on scale_units in order to keep a reasonable ratio between width and height and to keep the arrows in good shape (i.e. a reasonable head). Then, scale_units won't be propperly appreciated until the plot is resized (due to the differences in scaling depending on scale_units).

If scale is different than None, then the arrow lenght is not set to its default anymore, it follows the examples in the documentation.

Below there is a plot comparing different values of scale_units, with scale=1 and units set to its default value 'width'.

scale_units

It can be seen that the 'y' case, makes the arros look like dots, due to the difference in size between the U,V vectors which is around 5 and the y scale which goes between -150 and 150, whereas in the case of 'width' and 'lenght', 5 times the size of the plot axes makes the arrows huge.

like image 58
OriolAbril Avatar answered Oct 13 '22 22:10

OriolAbril