Plot a Heat Map onto Sphere - Matplotlib [duplicate] - python

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()

Related

How to set limits around (on both sides of) 0, in a polar Matplotlib plot (wedge diagram)

I am making a wedge diagram (plotting quasars in space, with RA as theta and Dec as r). I need to set the limits of a polar plot on both sides of 0. My limits should go from 45 degrees to 315 degrees with 0 degrees in between those two values (45-0-315). How do I do this?
This is my code:
import numpy as np
import matplotlib.pyplot as plt
theta = (np.pi/180)*np.array([340.555906,3.592373,32.473440,33.171584,35.463857,44.268397,339.362504,345.211906,346.485567,346.811945,348.672405,349.180736,349.370850,353.098343])
r = np.array([-32.906663,-33.842402,-32.425917,-32.677975, -30.701083,-31.460307,-32.909861,-30.802969,-33.683759,-32.207783,-33.068686,-33.820102,-31.438195,-31.920375])
colors = 'red'
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
c = ax.scatter(theta, r, c=colors, cmap='hsv', alpha=0.75)
plt.show()
If I put the limits:
ax.set_thetamin(45)
ax.set_thetamax(-45)
I get the correct slice of the diagram, but the wrong values on the theta axis (the axis now goes from -45-45 degrees).
If I put the limits:
ax.set_thetamin(45)
ax.set_thetamax(315)
I get the wrong slice of the diagram, but the correct values on the theta axis.
What to do?
It appears that matplotlib will only make the theta limits span across theta=0 if you have a positive and negative value for thetamin and thetamax. From the docstring for set_thetalim():
Values are wrapped in to the range [0, 2π] (in radians), so for example it is possible to do set_thetalim(-np.pi / 2, np.pi / 2) to have an axes symmetric around 0.
So setting:
ax.set_thetamin(45)
ax.set_thetamax(-45)
is the correct thing to do to get the plot you want. We can then modify the ticks later using a ticker.FuncFormatter to get the tick values you want.
For example:
import matplotlib.ticker as ticker
fmt = lambda x, pos: "{:g}".format(np.degrees(x if x >= 0 else x + 2 * np.pi))
ax.xaxis.set_major_formatter(ticker.FuncFormatter(fmt))
Which yields:
For completeness, here I put it all together in your script:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
theta = (np.pi/180)*np.array([340.555906,3.592373,32.473440,33.171584,35.463857,44.268397,339.362504,345.211906,346.485567,346.811945,348.672405,349.180736,349.370850,353.098343])
r = np.array([-32.906663,-33.842402,-32.425917,-32.677975, -30.701083,-31.460307,-32.909861,-30.802969,-33.683759,-32.207783,-33.068686,-33.820102,-31.438195,-31.920375])
colors = 'red'
fig = plt.figure()
ax = fig.add_subplot(111, polar=True)
c = ax.scatter(theta, r, c=colors, cmap='hsv', alpha=0.75)
ax.set_thetamin(45)
ax.set_thetamax(-45)
fmt = lambda x, pos: "{:g}".format(np.degrees(x if x >= 0 else x + 2 * np.pi))
ax.xaxis.set_major_formatter(ticker.FuncFormatter(fmt))
plt.show()

Radially 'sweep out' a 2D contour plot to create a 3D plot (Python, Matplotlib)

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()

Python Heat Distribution Plotting

i have been trying to simulate a flow distribution in a space but couldn't figure out how to properly do it in python using matplotlib,seaborn,plotly etc.
I have 3 variables
X : 0 to 0.4 ( meshed to 142 pieces )
Y : 0 to 0.45 ( meshed to 17767 pieces )
T : Values starting from 300 Kelvin and distrubited along the room ( 142x17767 )
To explain more, I have a rectangular area which is 142x17767 and i want to plot heat distrubition on every point. I have tried seaborn's heatmap and hist2d from matplotlib but these methods require x and y dimension to have same length.
What you need is I think pcolormesh. Here is a sample answer. You can replace T below by your actual T-values. The reason you get the error is because on such a 2d heat map, you need to create a meshgrid of your x and y points.
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6,6))
X = np.linspace(0, 0.4, 142)
Y = np.linspace(0, 0.45, 17767)
Xmesh, Ymesh = np.meshgrid(X,Y)
T = np.random.randint(300, 1000, Xmesh.shape)
plt.pcolormesh(X, Y, T)
plt.colorbar()
plt.show()

Make a Rotating Sphere in Python

I have made this code that applies the spherical harmonics in a spherical manner as I am trying to model stellar pulsation modes. Ideally, I'd like to be able to have an image that rotates that can be saved as a gif image. I have found a few examples of code for doing this but none of it seems to apply to my code or uses python packages that aren't available to me. I'm not sure if this is too far out of my range of skills in python as I'm very much a beginner.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
from scipy.special import sph_harm #import package to calculate spherical harmonics
theta = np.linspace(0, 2*np.pi, 100) #setting range for theta
phi = np.linspace(0, np.pi, 100) #setting range for phi
phi, theta = np.meshgrid(phi, theta) #setting the grid for phi and theta
#Setting the cartesian coordinates of the unit sphere
#Converting phi, theta, z to cartesian coordinates
x = np.sin(phi)*np.cos(theta)
y = np.sin(phi)*np.sin(theta)
z = np.cos(phi)
m, l = 4, 4 #m and l control the mode of pulsation and overall appearance of the figure
#Calculating the spherical harmonic Y(l,m) and normalizing it
figcolors = sph_harm(m, l, theta, phi).real
figmax, figmin = figcolors.max(), figcolors.min()
figcolors = (figcolors-figmin)/(figmax-figmin)
#Setting the aspect ratio to 1 which makes the sphere look spherical and not elongated
fig = plt.figure(figsize=plt.figaspect(1.)) #aspect ratio
axes = fig.add_subplot(111, projection='3d') #sets figure to 3d
#Sets the plot surface and colors of the figure where seismic is the color scheme
axes.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=cm.autumn(figcolors))
#yellow zones are cooler and compressed, red zones are warmer and expanded
#Turn off the axis planes so only the sphere is visible
axes.set_axis_off()
fig.suptitle('m=4 l=4', fontsize=18, x=0.52, y=.85)
plt.savefig('m4_l4.png') #saves a .png file of my figure
plt.show() #Plots the figure
#figure saved for m=1, 2, 3, 4 and l=2, 3, 5, 6 respectively then all 6 were put together to form a single figure
I've also got an image showing what my code outputs currently. It's just a still sphere, of course. Thank you in advance! sphere4_4
Change the last part of your code to generate a set of figures (see below). In this case I create num = 10 frames, you can change this number if you want. Then open a terminal and type
convert m4_l4*.png m4_l4.gif
And this is the result
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
from scipy.special import sph_harm #import package to calculate spherical harmonics
theta = np.linspace(0, 2*np.pi, 100) #setting range for theta
phi = np.linspace(0, np.pi, 100) #setting range for phi
phi, theta = np.meshgrid(phi, theta) #setting the grid for phi and theta
#Setting the cartesian coordinates of the unit sphere
#Converting phi, theta, z to cartesian coordinates
x = np.sin(phi)*np.cos(theta)
y = np.sin(phi)*np.sin(theta)
z = np.cos(phi)
m, l = 4, 4 #m and l control the mode of pulsation and overall appearance of the figure
#Calculating the spherical harmonic Y(l,m) and normalizing it
figcolors = sph_harm(m, l, theta, phi).real
figmax, figmin = figcolors.max(), figcolors.min()
figcolors = (figcolors-figmin)/(figmax-figmin)
#Setting the aspect ratio to 1 which makes the sphere look spherical and not elongated
fig = plt.figure(figsize=plt.figaspect(1.)) #aspect ratio
axes = fig.add_subplot(111, projection='3d') #sets figure to 3d
#Sets the plot surface and colors of the figure where seismic is the color scheme
axes.plot_surface(x, y, z, rstride=1, cstride=1, facecolors=cm.autumn(figcolors))
#yellow zones are cooler and compressed, red zones are warmer and expanded
axes.set_axis_off()
fig.suptitle('m=4 l=4', fontsize=18, x=0.52, y=.85)
for idx, angle in enumerate(np.linspace(0, 360, 10)):
axes.view_init(30, angle)
plt.draw()
#Turn off the axis planes so only the sphere is visible
plt.savefig('m4_l4-%04d.png' % idx) #saves a .png file of my figure
plt.show()

Python: How to revolve a surface around z axis and make a 3d plot?

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:

Categories