The normal vector is calculated with the cross product of two vectors on the plane, so it shoud be perpendicular to the plane. But as you can seein the plot the normal vector produced with quiver isn't perpendicular.
Is the calculation of the plane wrong, my normal vector or the way i plot the normal vector?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
points = [[3.2342, 1.8487, -1.8186],
[2.9829, 1.6434, -1.8019],
[3.4247, 1.5550, -1.8093]]
p0, p1, p2 = points
x0, y0, z0 = p0
x1, y1, z1 = p1
x2, y2, z2 = p2
ux, uy, uz = u = [x1-x0, y1-y0, z1-z0] #first vector
vx, vy, vz = v = [x2-x0, y2-y0, z2-z0] #sec vector
u_cross_v = [uy*vz-uz*vy, uz*vx-ux*vz, ux*vy-uy*vx] #cross product
point = np.array(p1)
normal = np.array(u_cross_v)
d = -point.dot(normal)
print('plane equation:\n{:1.4f}x + {:1.4f}y + {:1.4f}z + {:1.4f} = 0'.format(normal[0], normal[1], normal[2], d))
xx, yy = np.meshgrid(range(10), range(10))
z = (-normal[0] * xx - normal[1] * yy - d) * 1. / normal[2]
# plot the surface
plt3d = plt.figure().gca(projection='3d')
plt3d.quiver(x0, y0, z0, normal[0], normal[1], normal[2], color="m")
plt3d.plot_surface(xx, yy, z)
plt3d.set_xlabel("X", color='red', size=18)
plt3d.set_ylabel("Y", color='green', size=18)
plt3d.set_zlabel("Z", color='b', size=18)
plt.show()
Actually, your plot is 100% correct. The scale of your Z axis does not correspond to the same scale on X & Y axis. If you use a function to set the scale correct, you can see that:
...
plt3d.set_zlabel("Z", color='b', size=18)
# insert these lines
ax = plt.gca()
set_axis_equal(ax)
plt.show()
and the corresponding function from this post:
def set_axes_radius(ax, origin, radius):
'''
From StackOverflow question:
https://stackoverflow.com/questions/13685386/
'''
ax.set_xlim3d([origin[0] - radius, origin[0] + radius])
ax.set_ylim3d([origin[1] - radius, origin[1] + radius])
ax.set_zlim3d([origin[2] - radius, origin[2] + radius])
def set_axes_equal(ax, zoom=1.):
'''
Make axes of 3D plot have equal scale so that spheres appear as spheres,
cubes as cubes, etc.. This is one possible solution to Matplotlib's
ax.set_aspect("equal") and ax.axis("equal") not working for 3D.
input:
ax: a matplotlib axis, e.g., as output from plt.gca().
'''
limits = np.array([
ax.get_xlim3d(),
ax.get_ylim3d(),
ax.get_zlim3d(),
])
origin = np.mean(limits, axis=1)
radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0])) / zoom
set_axes_radius(ax, origin, radius)
Related
I am creating a circle and want to then develop a right-angle triangle, or any isometric form of a triangle. Whereby, I can take any line distance between two edges of the circle and draw arrows toward the peak point.
For example:
import numpy as np
import matplotlib.pyplot as plt
import math
theta = np.linspace(0, 2*np.pi, 100)
x1 = np.cos(theta)
y1 = np.sin(theta)
plt.plot(x1, y1)
for i in [min(y1), max(y1)]:
plt.plot(0, i, '-ok', mfc='C1', mec='C1')
plt.arrow(0,min(y1),0,2*max(y1),width=0.001,color='red',head_starts_at_zero=False)
plt.arrow(min(x1), (1/2)*min(y1), 2*max(x1), (1/2)*max(y1),width=0.001,color='red',head_starts_at_zero=False)
However, I cannot accurately get the distance between two points correct when i aim for any form of a triangle.
However, I can easily achieve it when setting y to 0 in the second arrow. Perhaps there is a general equation to do this?
Like this:
import numpy as np
import matplotlib.pyplot as plt
import math
theta = np.linspace(0, 2*np.pi, 100)
x1 = np.cos(theta)
y1 = np.sin(theta)
plt.plot(x1, y1)
x = np.array([0,120,240,0])
y = np.array([0,120,240,0])
x = np.cos( x * np.pi / 180 )
y = np.sin( y * np.pi / 180 )
plt.plot( x, y, color='red' )
plt.show()
Output:
In fact, if you choose ANY three angles, you'll get an inscribed triangle.
I need to plot contour and quiver plots of scalar and vector fields defined on an uneven grid in (r,theta) coordinates.
As a minimal example of the problem I have, consider the contour plot of a Stream function for a magnetic dipole, contours of such a function are streamlines of the corresponeding vector field (in this case, the magnetic field).
The code below takes an uneven grid in (r,theta) coordinates, maps it to the cartesian plane and plots a contour plot of the stream function.
import numpy as np
import matplotlib.pyplot as plt
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
psi = np.zeros((N_r, N_theta))
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig, ax = plt.subplots()
# ax.plot(x,y,'b.') # plot grid points
ax.set_aspect('equal')
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
plt.show()
For some reason though, the plot I get doesn't look right:
If I interchange x and y in the contour function call, I get the desired result:
Same thing happens when I try to make a quiver plot of a vector field defined on the same grid and mapped to the x-y plane, except that interchanging x and y in the function call no longer works.
It seems like I made a stupid mistake somewhere but I can't figure out what it is.
If psi = m * np.sin(theta_matrix)**2 / r_matrix
then psi increases as theta goes from 0 to pi/2 and psi decreases as r increases.
So a contour line for psi should increase in r as theta increases. That results
in a curve that goes counterclockwise as it radiates out from the center. This is
consistent with the first plot you posted, and the result returned by the first version of your code with
ax.contour(x, y, psi, 100, colors='black',levels=contour_levels)
An alternative way to confirm the plausibility of the result is to look at a surface plot of psi:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
r = np.logspace(0,1,200)
theta = np.linspace(0,np.pi/2,100)
N_r = len(r)
N_theta = len(theta)
# Polar to cartesian coordinates
theta_matrix, r_matrix = np.meshgrid(theta, r)
x = r_matrix * np.cos(theta_matrix)
y = r_matrix * np.sin(theta_matrix)
m = 5
# Stream function for a magnetic dipole
psi = m * np.sin(theta_matrix)**2 / r_matrix
contour_levels = m * np.sin(np.linspace(0, np.pi/2,40))**2.
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.set_aspect('equal')
ax.plot_surface(x, y, psi, rstride=8, cstride=8, alpha=0.3)
ax.contour(x, y, psi, colors='black',levels=contour_levels)
plt.show()
I'm looking at trying to obtain the Orientation of a plane that's plotted as a best fit line in a 3D scatter diagram using python 3.4.3 and Matplotlib. I currently have the data plotted in a 3D diagram with a plane going through the points and need a way to obtain the orientation of the plane.
Looking at getting the angle it is dipping at from the Z axis. Is there a shortcut i've missed that simply "prints" the orientation angle. Or would it be possible to create a trigonmetric feature from the roof of the box to the plane to get the angle. Also I have very little knowledge on mathplotlib therefore any help would be greatly appreciated.
def plane(x, y, params):
a = params[0]
b = params[1]
c = params[2]
z = a*x + b*y + c
return z
def error(params, points):
result = 0
for (x,y,z) in points:
plane_z = plane(x, y, params)
diff = abs(plane_z - z)
result += diff**2
return result
def cross(a, b):
return [a[1]*b[2] - a[2]*b[1],
a[2]*b[0] - a[0]*b[2],
a[0]*b[1] - a[1]*b[0]]
points = [(1.1,2.1,8.1),
(3.2,4.2,8.0),
(5.3,1.3,8.2),
(3.4,2.4,8.3),
(1.5,4.5,5.0)]
fun = functools.partial(error, points=points)
params0 = [0, 0, 0]
res = scipy.optimize.minimize(fun, params0)
a = res.x[0]
b = res.x[1]
c = res.x[2]
xs, ys, zs = zip(*points)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xs, ys, zs)
point = np.array([0.0, 0.0, c])
normal = np.array(cross([1,0,a], [0,1,b]))
d = -point.dot(normal)
xx, yy = np.meshgrid([-5,10], [-5,10])
z = (-normal[0] * xx - normal[1] * yy - d) * 1. /normal[2]
ax.plot_surface(xx, yy, z, alpha=0.6, color=[1,1,0])
ax.set_xlim(0,10)
ax.set_ylim(0,10)
ax.set_zlim(0,10)
plt.show()
If I recall correctly my knowlege of geometry you can calculate the angle with the z axis only by adding.
from math import acos, sqrt
cos_theta=1./sqrt(a*a+b*b+1)
theta=acos(cos_theta)
theta will be the angle between the vector normal to the plane and the z axis in radians. For example the XY plane have a theta of 0 and the ZY plane will have theta of pi/2
I have the following problem:
a have N points on a sphere specified by a array x, with x.shape=(N,3). This array contains their cartesian coordinates. Furthermore, at each point, I have a specified temperature. This quantity is saved in an array T, with T.shape=(N,).
Is there any straight forward way to map this temperature distribution into the plane using different colors?
If it simplifies the task, the position can also be given in polar coordinates (\theta,\phi).
To plot your data, you can use Basemap. The only problem is, that both contour and contourf routines needs gridded data. Here is example with naive (and slow) IDW-like interpolation on sphere. Any comments are welcome.
import numpy as np
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
def cart2sph(x, y, z):
dxy = np.sqrt(x**2 + y**2)
r = np.sqrt(dxy**2 + z**2)
theta = np.arctan2(y, x)
phi = np.arctan2(z, dxy)
theta, phi = np.rad2deg([theta, phi])
return theta % 360, phi, r
def sph2cart(theta, phi, r=1):
theta, phi = np.deg2rad([theta, phi])
z = r * np.sin(phi)
rcosphi = r * np.cos(phi)
x = rcosphi * np.cos(theta)
y = rcosphi * np.sin(theta)
return x, y, z
# random data
pts = 1 - 2 * np.random.rand(500, 3)
l = np.sqrt(np.sum(pts**2, axis=1))
pts = pts / l[:, np.newaxis]
T = 150 * np.random.rand(500)
# naive IDW-like interpolation on regular grid
theta, phi, r = cart2sph(*pts.T)
nrows, ncols = (90,180)
lon, lat = np.meshgrid(np.linspace(0,360,ncols), np.linspace(-90,90,nrows))
xg,yg,zg = sph2cart(lon,lat)
Ti = np.zeros_like(lon)
for r in range(nrows):
for c in range(ncols):
v = np.array([xg[r,c], yg[r,c], zg[r,c]])
angs = np.arccos(np.dot(pts, v))
idx = np.where(angs == 0)[0]
if idx.any():
Ti[r,c] = T[idx[0]]
else:
idw = 1 / angs**2 / sum(1 / angs**2)
Ti[r,c] = np.sum(T * idw)
# set up map projection
map = Basemap(projection='ortho', lat_0=45, lon_0=15)
# draw lat/lon grid lines every 30 degrees.
map.drawmeridians(np.arange(0, 360, 30))
map.drawparallels(np.arange(-90, 90, 30))
# compute native map projection coordinates of lat/lon grid.
x, y = map(lon, lat)
# contour data over the map.
cs = map.contourf(x, y, Ti, 15)
plt.title('Contours of T')
plt.show()
One way to do this is to set facecolors by mapping your heat data through the colormap.
Here's an example:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
u = np.linspace(0, 2 * np.pi, 80)
v = np.linspace(0, np.pi, 80)
# create the sphere surface
x=10 * np.outer(np.cos(u), np.sin(v))
y=10 * np.outer(np.sin(u), np.sin(v))
z=10 * np.outer(np.ones(np.size(u)), np.cos(v))
# simulate heat pattern (striped)
myheatmap = np.abs(np.sin(y))
ax.plot_surface(x, y, z, cstride=1, rstride=1, facecolors=cm.hot(myheatmap))
plt.show()
Here, my "heatmap" is just stripes along the y-axis, which I made using the function np.abs(np.sin(y)), but anything that goes form 0 to 1 will work (and, of course, it needs to match the shapes on x, etc.
I am trying to draw ellipses on a basemap projection. To draw a circle like polygon there is the tissot function used to draw Tissot's indicatrix' as illustrates the following example.
from mpl_toolkits.basemap import Basemap
x0, y0 = 35, -50
R = 5
m = Basemap(width=8000000,height=7000000, resolution='l',projection='aea',
lat_1=-40.,lat_2=-60,lon_0=35,lat_0=-50)
m.drawcoastlines()
m.tissot(x0, y0, R, 100, facecolor='g', alpha=0.5)
However, I am interested in plotting an ellipsis in the form (x-x0)**2/a**2 + (y-y0)**2/2 = 1. On the other hand, to draw an ellipsis on a regular Cartesian grid I can use the following sample code:
import pylab
from matplotlib.patches import Ellipse
fig = pylab.figure()
ax = pylab.subplot(1, 1, 1, aspect='equal')
x0, y0 = 35, -50
w, h = 10, 5
e = Ellipse(xy=(x0, y0), width=w, height=h, linewidth=2.0, color='g')
ax.add_artist(e)
e.set_clip_box(ax.bbox)
e.set_alpha(0.7)
pylab.xlim([20, 50])
pylab.ylim([-65, -35])
Is there a way to plot an ellipsis on a basemap projection with the an effect similar to tissot?
After hours analyzing the source code of basemap's tissot function, learning some properties of ellipses and lot's of debugging, I came with a solution to my problem. I've extended the basemap class with a new function called ellipse as follows,
from __future__ import division
import pylab
import numpy
from matplotlib.patches import Polygon
from mpl_toolkits.basemap import pyproj
from mpl_toolkits.basemap import Basemap
class Basemap(Basemap):
def ellipse(self, x0, y0, a, b, n, ax=None, **kwargs):
"""
Draws a polygon centered at ``x0, y0``. The polygon approximates an
ellipse on the surface of the Earth with semi-major-axis ``a`` and
semi-minor axis ``b`` degrees longitude and latitude, made up of
``n`` vertices.
For a description of the properties of ellipsis, please refer to [1].
The polygon is based upon code written do plot Tissot's indicatrix
found on the matplotlib mailing list at [2].
Extra keyword ``ax`` can be used to override the default axis instance.
Other \**kwargs passed on to matplotlib.patches.Polygon
RETURNS
poly : a maptplotlib.patches.Polygon object.
REFERENCES
[1] : http://en.wikipedia.org/wiki/Ellipse
"""
ax = kwargs.pop('ax', None) or self._check_ax()
g = pyproj.Geod(a=self.rmajor, b=self.rminor)
# Gets forward and back azimuths, plus distances between initial
# points (x0, y0)
azf, azb, dist = g.inv([x0, x0], [y0, y0], [x0+a, x0], [y0, y0+b])
tsid = dist[0] * dist[1] # a * b
# Initializes list of segments, calculates \del azimuth, and goes on
# for every vertex
seg = [self(x0+a, y0)]
AZ = numpy.linspace(azf[0], 360. + azf[0], n)
for i, az in enumerate(AZ):
# Skips segments along equator (Geod can't handle equatorial arcs).
if numpy.allclose(0., y0) and (numpy.allclose(90., az) or
numpy.allclose(270., az)):
continue
# In polar coordinates, with the origin at the center of the
# ellipse and with the angular coordinate ``az`` measured from the
# major axis, the ellipse's equation is [1]:
#
# a * b
# r(az) = ------------------------------------------
# ((b * cos(az))**2 + (a * sin(az))**2)**0.5
#
# Azymuth angle in radial coordinates and corrected for reference
# angle.
azr = 2. * numpy.pi / 360. * (az + 90.)
A = dist[0] * numpy.sin(azr)
B = dist[1] * numpy.cos(azr)
r = tsid / (B**2. + A**2.)**0.5
lon, lat, azb = g.fwd(x0, y0, az, r)
x, y = self(lon, lat)
# Add segment if it is in the map projection region.
if x < 1e20 and y < 1e20:
seg.append((x, y))
poly = Polygon(seg, **kwargs)
ax.add_patch(poly)
# Set axes limits to fit map region.
self.set_axes_limits(ax=ax)
return poly
This new function can be used promptly like in this example:
pylab.close('all')
pylab.ion()
m = Basemap(width=12000000, height=8000000, resolution='l', projection='stere',
lat_ts=50, lat_0=50, lon_0=-107.)
m.drawcoastlines()
m.fillcontinents(color='coral',lake_color='aqua')
# draw parallels and meridians.
m.drawparallels(numpy.arange(-80.,81.,20.))
m.drawmeridians(numpy.arange(-180.,181.,20.))
m.drawmapboundary(fill_color='aqua')
# draw ellipses
ax = pylab.gca()
for y in numpy.linspace(m.ymax/20, 19*m.ymax/20, 9):
for x in numpy.linspace(m.xmax/20, 19*m.xmax/20, 12):
lon, lat = m(x, y, inverse=True)
poly = m.ellipse(lon, lat, 3, 1.5, 100, facecolor='green', zorder=10,
alpha=0.5)
pylab.title("Ellipses on stereographic projection")
Which has the following outcome: