Reputation: 704
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).
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.
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)
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:
#-------------------------------------
# '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?
Upvotes: 8
Views: 1513
Reputation: 704
I found a work-around by developing a new function for the pyGMT package. A gallery example can be found here: https://www.pygmt.org/dev/gallery/seismology/velo_arrow_ellipse.html#sphx-glr-gallery-seismology-velo-arrow-ellipse-py
import pandas as pd
import pygmt
fig = pygmt.Figure()
df = pd.DataFrame(
data={
"x": [0, -8, 0, -5, 5, 0],
"y": [-8, 5, 0, -5, 0, -5],
"east_velocity": [0, 3, 4, 6, -6, 6],
"north_velocity": [0, 3, 6, 4, 4, -4],
"east_sigma": [4, 0, 4, 6, 6, 6],
"north_sigma": [6, 0, 6, 4, 4, 4],
"correlation_EN": [0.5, 0.5, 0.5, 0.5, -0.5, -0.5],
"SITE": ["0x0", "3x3", "4x6", "6x4", "-6x4", "6x-4"],
}
)
fig.velo(
data=df,
region=[-10, 8, -10, 6],
pen="0.6p,red",
uncertaintycolor="lightblue1",
line=True,
spec="e0.2/0.39/18",
frame=["WSne", "2g2f"],
projection="x0.8c",
vector="0.3c+p1p+e+gred",
)
fig.show()
Upvotes: 0
Reputation: 4171
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:
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:
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)
Maybe an additional warning or note should be added in the matplotlib docs, because I think this isn't really obvious at first glance.
Upvotes: 1