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:
Related
Given a center and two angles of a rotated ellipse of Arc from matplotlib.patches, I want to plot the two lines starting from the center of the Arc to the ends of the Arc.
Here is a piece of code that does that:
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
fig, ax = plt.subplots(1,1)
r = 2 #Radius
a = 0.2*r #width
b = 0.5*r #height
#center of the ellipse
x = 0.5
y = 0.5
ax.add_patch(Arc((x, y), a, b, angle = 20,
theta1=0, theta2=120,
edgecolor='b', lw=1.1))
#Now look for the ends of the Arc and manually set the limits
ax.plot([x,0.687],[y,0.567], color='r',lw=1.1)
ax.plot([x,0.248],[y,0.711], color='r',lw=1.1)
plt.show()
Which results in
.
Here the red lines were drawn looking carefully at the ends of the arc. However, as Arc does not allow to fill the arc for optimization, I wonder if there is a way to do it automatically for any center and angles.
According to Wikipedia an ellipse in its polar form looks like
Using this you may calculate the end points of the lines.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
fig, ax = plt.subplots(1,1)
r = 2 #Radius
a = 0.2*r #width
b = 0.5*r #height
#center of the ellipse
x = 0.5; y = 0.5
# angle
alpha = 20
ax.add_patch(Arc((x, y), a, b, angle = alpha,
theta1=0, theta2=120,
edgecolor='b', lw=1.1))
def ellipse(x0,y0,a,b,alpha,phi):
r = a*b/np.sqrt((b*np.cos(phi))**2 + (a*np.sin(phi))**2)
return [x0+r*np.cos(phi+alpha), y0+r*np.sin(phi+alpha)]
x1,y1 = ellipse(x, y, a/2., b/2., np.deg2rad(alpha), np.deg2rad(0))
ax.plot([x,x1],[y,y1], color='r',lw=1.1)
x2,y2 = ellipse(x, y, a/2., b/2., np.deg2rad(alpha), np.deg2rad(120))
ax.plot([x,x2],[y,y2], color='r',lw=1.1)
ax.set_aspect("equal")
plt.show()
Suppose I have a circle x**2 + y**2 = 20.
Now I want to plot the circle with n_dots number of dots in the circles perimeter in a scatter plot. So I created the code like below:
n_dots = 200
x1 = np.random.uniform(-20, 20, n_dots//2)
y1_1 = (400 - x1**2)**.5
y1_2 = -(400 - x1**2)**.5
plt.figure(figsize=(8, 8))
plt.scatter(x1, y1_1, c = 'blue')
plt.scatter(x1, y1_2, c = 'blue')
plt.show()
But this shows the dots not uniformly distributed all the places in the circle. The output is :
So how to create a circle with dots in scatter plot where all the dots are uniformly distributed in the perimeter of the circle?
A simple way to plot evenly-spaced points along the perimeter of a circle begins with dividing the whole circle into equally small angles where the angles from circle's center to all points are obtained. Then, the coordinates (x,y) of each point can be computed. Here is the code that does the task:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(8, 8))
n_dots = 120 # set number of dots
angs = np.linspace(0, 2*np.pi, n_dots) # angles to the dots
cx, cy = (50, 20) # center of circle
xs, ys = [], [] # for coordinates of points to plot
ra = 20.0 # radius of circle
for ang in angs:
# compute (x,y) for each point
x = cx + ra*np.cos(ang)
y = cy + ra*np.sin(ang)
xs.append(x) # collect x
ys.append(y) # collect y
plt.scatter(xs, ys, c = 'red', s=5) # plot points
plt.show()
The resulting plot:
Alternately, numpy's broadcasting nature can be used and shortened the code:
import matplotlib.pyplot as plt
import numpy as np
fig=plt.figure(figsize=(8, 8))
n_dots = 120 # set number of dots
angs = np.linspace(0, 2*np.pi, n_dots) # angles to the dots
cx, cy = (50, 20) # center of circle
ra = 20.0 # radius of circle
# with numpy's broadcasting feature...
# no need to do loop computation as in above version
xs = cx + ra*np.cos(angs)
ys = cy + ra*np.sin(angs)
plt.scatter(xs, ys, c = 'red', s=5) # plot points
plt.show()
for a very generalized answer that also works in 2D:
import numpy as np
import matplotlib.pyplot as plt
def u_sphere_pts(dim, N):
"""
uniform distribution points on hypersphere
from uniform distribution in n-D (<-1, +1>) hypercube,
clipped by unit 2 norm to get the points inside the insphere,
normalize selected points to lie on surface of unit radius hypersphere
"""
# uniform points in hypercube
u_pts = np.random.uniform(low=-1.0, high=1.0, size=(dim, N))
# n dimensional 2 norm squared
norm2sq = (u_pts**2).sum(axis=0)
# mask of points where 2 norm squared < 1.0
in_mask = np.less(norm2sq, np.ones(N))
# use mask to select points, norms inside unit hypersphere
in_pts = np.compress(in_mask, u_pts, axis=1)
in_norm2 = np.sqrt(np.compress(in_mask, norm2sq)) # only sqrt selected
# return normalized points, equivalently, projected to hypersphere surface
return in_pts/in_norm2
# show some 2D "sphere" points
N = 1000
dim = 2
fig2, ax2 = plt.subplots()
ax2.scatter(*u_sphere_pts(dim, N))
ax2.set_aspect('equal')
plt.show()
# plot histogram of angles
pts = u_sphere_pts(dim, 1000000)
theta = np.arctan2(pts[0,:], pts[1,:])
num_bins = 360
fig1, ax1 = plt.subplots()
n, bins, patches = plt.hist(theta, num_bins, facecolor='blue', alpha=0.5)
plt.show()
similar/related:
https://stackoverflow.com/questions/45580865/python-generate-an-n-dimensional-hypercube-using-rejection-sampling#comment78122144_45580865
Python Uniform distribution of points on 4 dimensional sphere
http://mathworld.wolfram.com/HyperspherePointPicking.html
Sampling uniformly distributed random points inside a spherical volume
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 want to plot N planes (say 10) parallel to XZ axis and equidistant to each other using python. If possible it would be nice to select the number of planes from user. It will be like, if user gives "20" then 20 planes will be drawn in 3D. This is what I did.But I would like to know is there a method to call each plane or like to get each plane's equation ??
import numpy as np
import itertools
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
plt3d = plt.figure().gca(projection='3d')
xx, zz = np.meshgrid(range(10), range(10))
yy =0.5
for _ in itertools.repeat(None, 20):
plt3d.plot_surface(xx, yy, zz)
plt.hold(True)
yy=yy+.1
plt.show()
Here is an example how to implement what you need in a very generic way.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
from pylab import meshgrid,linspace,zeros,dot,norm,cross,vstack,array,matrix,sqrt
def rotmatrix(axis,costheta):
""" Calculate rotation matrix
Arguments:
- `axis` : Rotation axis
- `costheta` : Rotation angle
"""
x,y,z = axis
c = costheta
s = sqrt(1-c*c)
C = 1-c
return matrix([[ x*x*C+c, x*y*C-z*s, x*z*C+y*s ],
[ y*x*C+z*s, y*y*C+c, y*z*C-x*s ],
[ z*x*C-y*s, z*y*C+x*s, z*z*C+c ]])
def plane(Lx,Ly,Nx,Ny,n,d):
""" Calculate points of a generic plane
Arguments:
- `Lx` : Plane Length first direction
- `Ly` : Plane Length second direction
- `Nx` : Number of points, first direction
- `Ny` : Number of points, second direction
- `n` : Plane orientation, normal vector
- `d` : distance from the origin
"""
x = linspace(-Lx/2,Lx/2,Nx)
y = linspace(-Ly/2,Ly/2,Ny)
# Create the mesh grid, of a XY plane sitting on the orgin
X,Y = meshgrid(x,y)
Z = zeros([Nx,Ny])
n0 = array([0,0,1])
# Rotate plane to the given normal vector
if any(n0!=n):
costheta = dot(n0,n)/(norm(n0)*norm(n))
axis = cross(n0,n)/norm(cross(n0,n))
rotMatrix = rotmatrix(axis,costheta)
XYZ = vstack([X.flatten(),Y.flatten(),Z.flatten()])
X,Y,Z = array(rotMatrix*XYZ).reshape(3,Nx,Ny)
dVec = (n/norm(n))*d
X,Y,Z = X+dVec[0],Y+dVec[1],Z+dVec[2]
return X,Y,Z
if __name__ == "__main__":
# Plot as many planes as you like
Nplanes = 10
# Set color list from a cmap
colorList = cm.jet(linspace(0,1,Nplanes))
# List of Distances
distList = linspace(-10,10,Nplanes)
# Plane orientation - normal vector
normalVector = array([0,1,1]) # Y direction
# Create figure
fig = plt.figure()
ax = fig.gca(projection='3d')
# Plotting
for i,ypos in enumerate(linspace(-10,10,10)):
# Calculate plane
X,Y,Z = plane(20,20,100,100,normalVector,distList[i])
ax.plot_surface(X, Y, Z, rstride=5, cstride=5,
alpha=0.8, color=colorList[i])
# Set plot display parameters
ax.set_xlabel('X')
ax.set_xlim(-10, 10)
ax.set_ylabel('Y')
ax.set_ylim(-10, 10)
ax.set_zlabel('Z')
ax.set_zlim(-10, 10)
plt.show()
If you need to rotate the plane around the normal vector, you can also use the rotation matrix for that.
Cheers
I am trying to use python3 and matplotlib (version 1.4.0) to plot a scalar function defined on the surface of a sphere. I would like to have faces distributed relatively evenly over the sphere, so I am not using a meshgrid. This has led me to use plot_trisurf to plot my function. I have tested it with a trivial scalar function, and am having the problem that there are rendering artefacts along the edges of the faces:
The code I used to create the plot is below:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.tri as mtri
from scipy.spatial import ConvexHull
def points_on_sphere(N):
""" Generate N evenly distributed points on the unit sphere centered at
the origin. Uses the 'Golden Spiral'.
Code by Chris Colbert from the numpy-discussion list.
"""
phi = (1 + np.sqrt(5)) / 2 # the golden ratio
long_incr = 2*np.pi / phi # how much to increment the longitude
dz = 2.0 / float(N) # a unit sphere has diameter 2
bands = np.arange(N) # each band will have one point placed on it
z = bands * dz - 1 + (dz/2) # the height z of each band/point
r = np.sqrt(1 - z*z) # project onto xy-plane
az = bands * long_incr # azimuthal angle of point modulo 2 pi
x = r * np.cos(az)
y = r * np.sin(az)
return x, y, z
def average_g(triples):
return np.mean([triple[2] for triple in triples])
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = points_on_sphere(2**12)
Triples = np.array(list(zip(X, Y, Z)))
hull = ConvexHull(Triples)
triangles = hull.simplices
colors = np.array([average_g([Triples[idx] for idx in triangle]) for
triangle in triangles])
collec = ax.plot_trisurf(mtri.Triangulation(X, Y, triangles),
Z, shade=False, cmap=plt.get_cmap('Blues'), array=colors,
edgecolors='none')
collec.autoscale()
plt.show()
This problem appears to have been discussed in this question, but I can't seem to figure out how to set the edgecolors to match the facecolors. The two things I've tried are setting edgecolors='face' and calling collec.set_edgecolors() with a variety of arguments, but those throw AttributeError: 'Poly3DCollection' object has no attribute '_facecolors2d'.
How am I supposed to set the edgecolor equal to the facecolor in a trisurf plot?
You can set antialiased argument of plot_trisurf() to False. Here is the result: