I have a set of data that I want to use to produce a contour plot in polar co-ordinates using Matplotlib.
My data is the following:
theta - 1D array of angle values
radius - 1D array of radius values
value - 1D array of values that I want to use for the contours
These are all 1D arrays that align properly - eg:
theta radius value
30 1 2.9
30 2 5.3
35 5 9.2
That is, all of the values are repeated enough times so that each row of this 'table' of three variables defines one point.
How can I create a polar contour plot from these values? I've thought about converting the radius and theta values to x and y values and doing it in cartesian co-ordinates, but the contour function seems to require 2D arrays, and I can't quite understand why.
Any ideas?
Matplotlib's contour() function expects data to be arranged as a 2D grid of points and corresponding grid of values for each of those grid points. If your data is naturally arranged in a grid you can convert r, theta to x, y and use contour(r*np.cos(theta), r*np.sin(theta), values) to make your plot.
If your data isn't naturally gridded, you should follow Stephen's advice and used griddata() to interpolate your data on to a grid.
The following script shows examples of both.
import pylab as plt
from matplotlib.mlab import griddata
import numpy as np
# data on a grid
r = np.linspace(0, 1, 100)
t = np.linspace(0, 2*np.pi, 100)
r, t = np.meshgrid(r, t)
z = (t-np.pi)**2 + 10*(r-0.5)**2
plt.subplot(121)
plt.contour(r*np.cos(t), r*np.sin(t), z)
# ungrid data, then re-grid it
r = r.flatten()
t = t.flatten()
x = r*np.cos(t)
y = r*np.sin(t)
z = z.flatten()
xgrid = np.linspace(x.min(), x.max(), 100)
ygrid = np.linspace(y.min(), y.max(), 100)
xgrid, ygrid = np.meshgrid(xgrid, ygrid)
zgrid = griddata(x,y,z, xgrid, ygrid)
plt.subplot(122)
plt.contour(xgrid, ygrid, zgrid)
plt.show()
I don't know if it's possible to do a polar contour plot directly, but if you convert to cartesian coordinates you can use the griddata function to convert your 1D arrays to 2D.
Related
I have three 1D arrays, which represent radius, height, and an intensity measured at that point. I have plotted these to create a 2D contour map. A simple example of the way in which the data is stored is below:
import numpy as np
import matplotlib.pyplot as plt
x = [1,1,1,2,2,2,3,3,3]
y = [1,2,3,1,2,3,1,2,3]
intensity = [5,6,8,9,9,11,15,5,2]
plt.xlabel('Radius')
plt.ylabel('Height')
plt.tricontourf(x,y,intensity)
plt.colorbar(label='Intensity')
plt.show()
(I have had to use plt.tricontourf rather than plt.contour, since the z data is not 2D)
I am looking to create a 3D plot by 'sweeping' the 2D plot through 360 degrees, creating a disk which is azimuthally symmetric. The image below illustrates what I am trying to do...
...with the data interpolated smoothly through the 360 degrees.
There are a couple of similar questions, notably this one, but this does not use three sets of data to create the contours.
Technically you cannot rotate a 2D plot and get a 3D surface. You can only rotate a 2D curve and get a 3D surface. If this is the case, you could do it as:
import numpy as np
from matplotlib import cm
import matplotlib.pyplot as plt
fig = plt.figure(figsize = (8, 6))
ax = fig.add_subplot(projection='3d')
N = 100
r = np.linspace(0, 1, N)
z = np.sqrt(1 - r**2)
intensity = np.linspace(0, 1, N).reshape(1, -1)
theta = np.linspace(0, 2*np.pi-1e-3, N)
X = np.outer(np.cos(theta), r)
Y = np.outer(np.sin(theta), r)
Z = np.repeat(z.reshape(1, -1), N, axis = 0)
surf = ax.plot_surface(X, Y, Z, facecolors=cm.jet(np.repeat(intensity, N, axis = 0)))
ax.axes.set_zlim3d(-1, 1)
plt.show()
In the code I rotated a curve to create half a unit sphere and color it according to intensity:
to
If you insist on plotting all the points, I would suggest a 3d scatter plot, I did some linear interpolation to show more points than the original 9:
from scipy.interpolate import interp2d
x = [1,1,1,2,2,2,3,3,3]
y = [1,2,3,1,2,3,1,2,3]
intensity = [5,6,8,9,9,11,15,5,2]
# number of points to interpolate in 3d space
N = 36
# number of points to interpolate in 2d space
N_2d = 10
f = interp2d(x, y, intensity)
# sample along the radius
r = np.linspace(1,3,N_2d)
# sample along z
z = np.linspace(1,3,N_2d)
intensity = f(r, z)
r,z = np.meshgrid(r, z)
theta = np.linspace(0, 2*np.pi, N)
X = np.outer(np.cos(theta), r)
Y = np.outer(np.sin(theta), r)
Z = np.repeat(z.reshape(1, -1), N, axis = 0)
fig = plt.figure(figsize = (10, 6))
ax = fig.add_subplot(projection='3d')
ax.scatter3D(X, Y, Z, c=np.tile(intensity.T, N).T, alpha = 0.5)
plt.show()
This question already has an answer here:
How to draw a solid of revolution of a polynomial function around the y-axis?
(1 answer)
Closed 3 years ago.
I have created a 2D plot using matplotlib in Python, an example being this one:
It has been produced using 2 lists:
import matplotlib.pyplot as plt
import numpy as np
plt.plot(X, Y) #X and Y are lists, containing the x and y coordinates of points respectively
plt.show()
Now I want to create a revolution of that plot around the Y-axis, and visualizing it in a way that the Y-axis is vertical. How can that be done using matplotlib?
If you have a curve defined as a collection of x and y points in two 1D arrays and you want to revolve them about the y axis you simply need to construct 2D arrays to satisfy matplotlib's Axes3D.plot_surface by taking the outer products, using np.outer(), of x with np.cos(theta) and np.sin(theta) for theta in [0, 2π]. This will give you a collection of cartesian points in xy space, which will represent the circles created by revolving each original point about the z axis. Constructing the z array is a bit tricky because of the shape expected by plot_surface().
Here is a complete example which demonstrates this method and compares it with the original 2D plot
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
n = 100
fig = plt.figure(figsize=(12,6))
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122,projection='3d')
y = np.linspace(np.pi/8, np.pi*4/5, n)
x = np.sin(y)
t = np.linspace(0, np.pi*2, n)
xn = np.outer(x, np.cos(t))
yn = np.outer(x, np.sin(t))
zn = np.zeros_like(xn)
for i in range(len(x)):
zn[i:i+1,:] = np.full_like(zn[0,:], y[i])
ax1.plot(x, y)
ax2.plot_surface(xn, yn, zn)
plt.show()
I want to plot 720 x 180 values of theta and phi into
theta range = (-180 to 180 with 0.5 step)
phi range = (0 to -90 with 0.5 step)
This is the example of dataset that I have:
Theta Phi Values
-180 0 0.2
-180 0.5 0.5
... ... ...
-180 -90 1.1
-179.5 0 0.92
... ... ...
0 -90 0.6
... ... ...
180 -89.5 0.17
180 -90 0.12
So eventually, I want to get a similar plot like this one:
I know how to create the half sphere with the code below, but how can assign the values from my dataframe?
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
# Create a sphere
r = 2
pi = np.pi
cos = np.cos
sin = np.sin
altitude
phi, theta = np.mgrid[0.0:0.5*pi:180j, 0.0:2.0*pi:720j] # phi = alti, theta = azi
x = r*sin(phi)*cos(theta)
y = r*sin(phi)*sin(theta)
z = r*cos(phi)
#Set colours and render
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(
x, y, z, rstride=4, cstride=4, color='w', alpha=0.1, linewidth=0)
ax.set_xlim([-2.2,2.2])
ax.set_ylim([-2.2,2.2])
ax.set_zlim([0,3])
ax.set_aspect("equal")
ax.plot_wireframe(x, y, z, color="k")
the code generate this one
Axes3D.plot_surface accepts 2D arrays as inputs. It provides the facecolors argument, which accepts an array of the same shape as the input arrays. This array should have the color for each face as rgba tuple in it. One can therefore normalize the array values to the range up to 1 and supply it the a colormap from matplotlib.cm.
The remaining problem is then to obtain this array from the 3 column list which is provided. Given a the datatable of length n*m where the first column denotes x values, second y values and the third some value, and where the sorting is first by x and then by y. One can then reshape the last column to an (n,m) array, where n is the number of x values and m of y values, using .reshape((m,n)).T.
Some further remarks:
In the solution below, I needed to mimic this array and directly used angles in radiant, instead of degrees.
The number of points, 180*720 seems a bit high. In order for the window not to take ages to rotate, I decreased that number.
I renamed the angles, such that they match with the usual textbook definition, phi = azimuthal angle, theta=inclination angle (from z axis).
The use of plot_wireframe may not make too much sense, since it will hide the surface below. If a wireframe is desired, one can play with the number of points to be drawn and the linewidth keyword argument. Setting linewidth to something big, like 3 or 5 makes the surface look nice, setting it to 1 leaves some wireframe look.
Here is the complete solution.
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
#theta inclination angle
#phi azimuthal angle
n_theta = 50 # number of values for theta
n_phi = 200 # number of values for phi
r = 2 #radius of sphere
theta, phi = np.mgrid[0.0:0.5*np.pi:n_theta*1j, 0.0:2.0*np.pi:n_phi*1j]
x = r*np.sin(theta)*np.cos(phi)
y = r*np.sin(theta)*np.sin(phi)
z = r*np.cos(theta)
# mimic the input array
# array columns phi, theta, value
# first n_theta entries: phi=0, second n_theta entries: phi=0.0315..
inp = []
for j in phi[0,:]:
for i in theta[:,0]:
val = 0.7+np.cos(j)*np.sin(i+np.pi/4.)# put something useful here
inp.append([j, i, val])
inp = np.array(inp)
print inp.shape
print inp[49:60, :]
#reshape the input array to the shape of the x,y,z arrays.
c = inp[:,2].reshape((n_phi,n_theta)).T
print z.shape
print c.shape
#Set colours and render
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
#use facecolors argument, provide array of same shape as z
# cm.<cmapname>() allows to get rgba color from array.
# array must be normalized between 0 and 1
ax.plot_surface(
x,y,z, rstride=1, cstride=1, facecolors=cm.hot(c/c.max()), alpha=0.9, linewidth=1)
ax.set_xlim([-2.2,2.2])
ax.set_ylim([-2.2,2.2])
ax.set_zlim([0,4.4])
ax.set_aspect("equal")
#ax.plot_wireframe(x, y, z, color="k") #not needed?!
plt.savefig(__file__+".png")
plt.show()
Is there anyway to get the polygon data (i.e triangle edges) from the plot_trisurf function? Since it takes x,y and z data and creates a Delaunay triangulation it must have this data somewhere...?
Something that can be used with the below example would be great.
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
n_angles = 36
n_radii = 8
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
z = np.sin(-x*y)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2)
plt.show()
I'd like to use matplotlib for this, as it uses a Delaunay algorithm that is more suited to 3D data. From what I can workout, doesn't parametrise it into 2D, and is easily available on a much wider range of hardware without anything close to the same amount of faff, unlike mayavi. Plotly could also be an option, although I believe this requires the data to be parametrised into 2D, which is not ideal for my application.
I'm aware there are Delaunay libraries in python scipy that will do this, but again the parametrisation of the data is the problem.
You can look at the source code of plot_trisurf to find out how matplotlib does the triangulation.
It basically performs a triangulation on a 2D grid and takes over the z component from the input.
The 2D triangulation is done by the matplotlib.tri.triangulation.Triangulation class.
The following could do what you want, giving you an array of the vertices.
from matplotlib.tri.triangulation import Triangulation
import numpy as np
n_angles = 36
n_radii = 8
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
z = np.sin(-x*y)
tri, args, kwargs = Triangulation.get_from_args_and_kwargs(x, y, z)
triangles = tri.get_masked_triangles()
xt = tri.x[triangles][..., np.newaxis]
yt = tri.y[triangles][..., np.newaxis]
zt = z[triangles][..., np.newaxis]
verts = np.concatenate((xt, yt, zt), axis=2)
print verts
Poly3DCollection is for this purpose, plotting 3D polygons without interpolation. It accepts a list of the Polygon vertices' coordinates, and you simply call ax.add_collection3d(collection) to add them to the axes.
I want to get 2d and 3d plots as shown below.
The equation of the curve is given.
How can we do so in python?
I know there may be duplicates but at the time of posting
I could not fine any useful posts.
My initial attempt is like this:
# Imports
import numpy as np
import matplotlib.pyplot as plt
# to plot the surface rho = b*cosh(z/b) with rho^2 = r^2 + b^2
z = np.arange(-3, 3, 0.01)
rho = np.cosh(z) # take constant b = 1
plt.plot(rho,z)
plt.show()
Some related links are following:
Rotate around z-axis only in plotly
The 3d-plot should look like this:
Ok so I think you are really asking to revolve a 2d curve around an axis to create a surface. I come from a CAD background so that is how i explain things.
and I am not the greatest at math so forgive any clunky terminology. Unfortunately you have to do the rest of the math to get all the points for the mesh.
Heres your code:
#import for 3d
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
change arange to linspace which captures the endpoint otherwise arange will be missing the 3.0 at the end of the array:
z = np.linspace(-3, 3, 600)
rho = np.cosh(z) # take constant b = 1
since rho is your radius at every z height we need to calculate x,y points around that radius. and before that we have to figure out at what positions on that radius to get x,y co-ordinates:
#steps around circle from 0 to 2*pi(360degrees)
#reshape at the end is to be able to use np.dot properly
revolve_steps = np.linspace(0, np.pi*2, 600).reshape(1,600)
the Trig way of getting points around a circle is:
x = r*cos(theta)
y = r*sin(theta)
for you r is your rho, and theta is revolve_steps
by using np.dot to do matrix multiplication you get a 2d array back where the rows of x's and y's will correspond to the z's
theta = revolve_steps
#convert rho to a column vector
rho_column = rho.reshape(600,1)
x = rho_column.dot(np.cos(theta))
y = rho_column.dot(np.sin(theta))
# expand z into a 2d array that matches dimensions of x and y arrays..
# i used np.meshgrid
zs, rs = np.meshgrid(z, rho)
#plotting
fig, ax = plt.subplots(subplot_kw=dict(projection='3d'))
fig.tight_layout(pad = 0.0)
#transpose zs or you get a helix not a revolve.
# you could add rstride = int or cstride = int kwargs to control the mesh density
ax.plot_surface(x, y, zs.T, color = 'white', shade = False)
#view orientation
ax.elev = 30 #30 degrees for a typical isometric view
ax.azim = 30
#turn off the axes to closely mimic picture in original question
ax.set_axis_off()
plt.show()
#ps 600x600x600 pts takes a bit of time to render
I am not sure if it's been fixed in latest version of matplotlib but the setting the aspect ratio of 3d plots with:
ax.set_aspect('equal')
has not worked very well. you can find solutions at this stack overflow question
Only rotate the axis, in this case x
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
np.seterr(divide='ignore', invalid='ignore')
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = np.linspace(-3, 3, 60)
rho = np.cosh(x)
v = np.linspace(0, 2*np.pi, 60)
X, V = np.meshgrid(x, v)
Y = np.cosh(X) * np.cos(V)
Z = np.cosh(X) * np.sin(V)
ax.set_xlabel('eje X')
ax.set_ylabel('eje Y')
ax.set_zlabel('eje Z')
ax.plot_surface(X, Y, Z, cmap='YlGnBu_r')
plt.plot(x, rho, 'or') #Muestra la curva que se va a rotar
plt.show()
The result: