Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get coordinates of quiver arrow (tip and bottom) when plotting in 'uv' mode

What I am trying to do

I would like to be able to get the coordinate of the quiver arrow when plotting in 'uv' mode in order to re-use this data to plot other shapes (e.g. ellipse).

The problem

This issue is also related to this post. In this post, the answers mention using the ._paths quiver variable to get the coordinate of the arrow. However, there are no indications about how to do it.

Does someone have a solution to access the coordinates associated with the top and bottom of the arrow in a ‘uv’ plotting mode? There are plenty of variables in q._paths and I cannot see which one is relevant.

Code for reproduction

The code below work perfectly fine in 'xy' mode:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from matplotlib.patches import Ellipse
import matplotlib.transforms as transforms

#-------------------------------------
# Variable definition
colsec = 'royalblue'
colvec = 'salmon'
colellipse = 'limegreen'
x = np.array([ 0.00789308, -0.0773587 ,  0.03353797, -0.06185714, -0.13095092,
        0.03280368,  0.04775701, -0.08124051, -0.02894444, -0.02834356,
       -0.1457362 , -0.00628834,  0.09627607])
y = np.array([-0.03668553,  0.05931522, -0.04041772, -0.00866234, -0.00539877,
       -0.14787117, -0.21553271, -0.15741139, -0.1417963 , -0.00887117,
        0.02207362, -0.11979755, -0.28635583])
meanx = np.mean(x)
meany = np.mean(y)

# ellipse parameter
ell_radius_x = 0.54
ell_radius_y = 1.30
scale_x = 0.07
scale_y = 0.1

#-------------------------------------
# 'xy' plot

posx1 = 0
posy1 = 0
    
# Plot
fig, ax = plt.subplots(figsize=(8, 8))
plt.scatter(x,y,color='blue')

# Quiver plot
Qv = ax.quiver(posx1, posy1, meanx,  meany,
              angles='xy', scale_units='xy', scale=1,
              color='black')

# Basic ellipse definition
ellipse = Ellipse((0, 0),
        width=ell_radius_x * 2,
        height=ell_radius_y * 2,
        facecolor='none',
        edgecolor='red')

center=(meanx + posx1, meany + posy1)

# Transformation of the ellipse according to external parameters (obtained from various statistics on the data)
transf = transforms.Affine2D() \
        .rotate_deg(45) \
        .scale(scale_x, scale_y) \
        .translate(*center)
ellipse.set_transform(transf + ax.transData)
    
# Plot of the ellipse
ax.plot(*center,'x',color='g',markersize=12)    
    
ax.add_patch(ellipse)

We got the expected outcome: ellipse and quiver 'xy' mode

Now when I switch to 'uv' mode (my quiver position has a different unit), I cannot reproduce the same plot, although I tried playing with scaling factor. The code below gives me this outcome: enter image description here

#-------------------------------------
# 'uv' plot (variables are defined previously)

# Scale factor for quiver and ellipse plot
scalefac = 2

posx1 = np.array(-12.633)
posy1 = np.array(57.533)

# Plot
fig, ax = plt.subplots(figsize=(8, 8))
plt.scatter(posx1,posy1,color='blue')

Qv = ax.quiver(posx1, posy1, meanx*scalefac,  meany*scalefac,
              scale=1, scale_units='width',
              color='black')

# Basic ellipse definition
ellipse = Ellipse((0, 0),
        width=ell_radius_x * 2,
        height=ell_radius_y * 2,
        facecolor='none',
        edgecolor='red')

# Transformation of the ellipse according to external parameters (obtained from various statistics on the data)
center=(meanx*scalefac + posx1, meany*scalefac + posy1)
transf = transforms.Affine2D() \
        .rotate_deg(45) \
        .scale(scale_x*scalefac, scale_y*scalefac) \
        .translate(*center)
ellipse.set_transform(transf + ax.transData)
    
# Plot of the ellipse
ax.plot(*center,'x',color='g',markersize=12)    
    
ax.add_patch(ellipse)

Qv._paths doesn't return a variable easy to understand:

print(Qv._paths)
[Path(array([[ 0.00357682, -0.00112643],
       [-0.03897025, -0.13622912],
       [-0.03069018, -0.13490515],
       [-0.05268492, -0.1672941 ],
       [-0.05215112, -0.12814659],
       [-0.0461239 , -0.13397627],
       [-0.00357682,  0.00112643],
       [ 0.00357682, -0.00112643]]), None)]

I guess the scaling information I need is somewhere in Qv._paths but it is not clear to me where. The idea would be to have a robust method so I could change the scaling associated with my variable scalefac. Any suggestions?

like image 781
lhoupert Avatar asked Jul 08 '20 11:07

lhoupert


1 Answers

After taking another look at the doc, I suddenly figured it out. Let me plot a dummy example, for easier understanding.

fig, ax = plt.subplots(figsize=(8, 8))
plt.xlim(-12.8,-12.6)
plt.ylim(57.2,57.6)

x1, y1 = -12.633, 57.533
x2, y2 = -12.7, 57.4
angle = np.arctan((y2-y1)/(x2-x1))
D = np.sqrt((x2-x1)**2 + (y2-y1)**2)
U, V = D*np.cos(angle), D*np.sin(angle)
ax.scatter(x2, y2, marker='x', s=100, color='k')
Qv1 = ax.quiver(x1, y1, -U, -V, angles='uv', scale=1, scale_units='xy', color='black')
Qv2 = ax.quiver(x1, y1, -U, -V, angles='xy', scale=1, scale_units='xy', color='red')

Consider (x2, y2) analogous to the center of a hypothetical ellipse. We can calculate the angle and the U, V components with simple vector math. Plotting the above gives:

Qv1 and Qv2

Note how the quiver in uv angles is off by just a bit, same as the example in the question. However, if you read the documentation in matplotlib's docs, it says:

'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).

The arrow's axis is independent of the x,y axis in uv mode, and this axis has an aspect ratio of 1. So naturally, if your plot has an aspect ratio of 1, the arrow should match perfectly!

plt.xlim(-12.8,-12.4)
plt.ylim(57.2,57.6)

Under this xlim and ylim setting, if I run my previous dummy plot, we'd get:

Matching Qv

Going back to your example, you need to change scale_units to 'xy' and simply set an xlim, ylim with an aspect ratio of 1.

#-------------------------------------
# 'uv' plot (variables are defined previously)

# Scale factor for quiver and ellipse plot
scalefac = 2

posx1 = np.array(-12.633)
posy1 = np.array(57.533)

# Plot
fig, ax = plt.subplots(figsize=(8, 8))
plt.scatter(posx1,posy1,color='blue')

Qv = ax.quiver(posx1, posy1, meanx*scalefac,  meany*scalefac,
              angles='uv', scale=1, scale_units='xy',
              color='black')

# Basic ellipse definition
ellipse = Ellipse((0, 0),
        width=ell_radius_x * 2,
        height=ell_radius_y * 2,
        facecolor='none',
        edgecolor='red')

# Transformation of the ellipse according to external parameters (obtained from various statistics on the data)
center=(meanx*scalefac + posx1, meany*scalefac + posy1)
transf = transforms.Affine2D() \
        .rotate_deg(45) \
        .scale(scale_x*scalefac, scale_y*scalefac) \
        .translate(*center)
ellipse.set_transform(transf + ax.transData)
    
# Plot of the ellipse
ax.plot(*center,'x',color='g',markersize=12)    
    
ax.add_patch(ellipse)
plt.xlim(-13,-12.5)
plt.ylim(57.1, 57.6)

Correct Qv

Maybe an additional warning or note should be added in the matplotlib docs, because I think this isn't really obvious at first glance.

like image 149
Mercury Avatar answered Nov 15 '22 05:11

Mercury