Plot sphere in matplotlib from non-organised data - python

I have some Fortran code which outputs the polar coordinates of a grid on the surface of a sphere in theta, phi format. It also outputs a value associated with each of these points (specifically meant to represent the voltage at that point on the sphere's surface).
Now I want to read this data into Python, plot a sphere, and then colour it according to the voltage data values. I know how to do this for a latitude-longitude grid, but my grid points are not ordered in any specific way.
The code I'm trying is as follows:
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
import option_d
import numpy as np
# Create a sphere
r = 1.0
pi = np.pi
cos = np.cos
sin = np.sin
#Read in grid points
data = np.genfromtxt('grid.txt')
phi, theta = np.hsplit(data, 2)
#Convert grid points to cartesian
x = r*sin(phi)*cos(theta)
y = r*sin(phi)*sin(theta)
z = r*cos(phi)
#Import data from initial state
colorfunction = np.genfromtxt('sphere_init.txt')
print np.shape(colorfunction)
#Normalise the colour map to the initial data
newcm = option_d.test_cm
norm=colors.Normalize(vmin = -np.max(colorfunction), vmax = np.max(colorfunction), clip = False)
#Plot the surface
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(
x,y,z,rstride=1,cstride=1,cmap=newcm,facecolors=newcm(norm(colorfunction)))
#Set axes and display or save
ax.set_aspect("equal")
plt.tight_layout()
plt.show()
The file 'grid.txt' contains two columns, each 770 in length, representing the phi, theta coordinates of each point. The file 'sphere_init.txt' contains a single column of length 770, which are the corresponding data values. However, this does not work - it just throws error messages at me. Is it even possible to plot a sphere from disordered grid points? Any help much appreciated. Thanks.
Edit
Here is the error message:
Traceback (most recent call last):
File "sphere.py", line 43, in <module>
x,y,z,rstride=1,cstride=1, cmap=newcm,facecolors=newcm(norm(colorfunction)))
File "/usr/lib/pymodules/python2.7/mpl_toolkits/mplot3d/axes3d.py", line 1611, in plot_surface
colset.append(fcolors[rs][cs])
IndexError: index out of bounds

I believe I have solved my problem. I read in my irregular grid data, and then also create a regular latitude-longitude grid. I then interpolate from the irregular grid to the lat-long grid:
import matplotlib.pyplot as plt
import matplotlib.mlab as ml
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
import option_d
import numpy as np
import time
#Read in lebedev grid points
data = np.genfromtxt('grid.txt')
u, v = np.hsplit(data, 2)
phi, theta = u[:,0], v[:,0]
#Import data from initial state
colorfunction = np.genfromtxt('sphere_init.txt')
#Generate a lat-long grid to interpolate on
p = np.linspace(0,np.pi, 770)
t = np.linspace(-np.pi, np.pi, 770)
p, t = np.meshgrid(p, t)
#Interpolate using delaunay triangularization
zi = ml.griddata(phi, theta, colorfunction, p, t)
#Convert the lat-long grid points to cartesian
x = np.sin(p)*np.cos(t)
y = np.sin(p)*np.sin(t)
z = np.cos(p)
#Normalize the interpolated colourfunction
#Use fancy new colourmap
newcm = option_d.test_cm
norm=colors.Normalize(vmin = -np.max(zi), vmax = np.max(zi), clip = False)
#Plot the surface
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(
x,y,z,rstride=1,cstride=1, cmap=newcm,facecolors=newcm(norm(zi)))
#Display
ax.set_aspect("equal")
ax.set_xlim([-1,1])
ax.set_ylim([-1,1])
ax.set_zlim([-1,1])
plt.tight_layout()
plt.show()
Edit
I have run into a new problem with this method. It causes a chunk to be missing from the back of my sphere:
Any ideas why?

Related

How to fill area under 3D circular line plot in Python [duplicate]

I have a 3d plot made using matplotlib. I now want to fill the vertical space between the drawn line and the x,y axis to highlight the height of the line on the z axis. On a 2d plot this would be done with fill_between but there does not seem to be anything similar for a 3d plot. Can anyone help?
here is my current code
from stravalib import Client
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
... code to get the data ....
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
zi = alt
x = df['x'].tolist()
y = df['y'].tolist()
ax.plot(x, y, zi, label='line')
ax.legend()
plt.show()
and the current plot
just to be clear I want a vertical fill to the x,y axis intersection NOT this...
You're right. It seems that there is no equivalent in 3D plot for the 2D plot function fill_between. The solution I propose is to convert your data in 3D polygons. Here is the corresponding code:
import math as mt
import matplotlib.pyplot as pl
import numpy as np
import random as rd
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
# Parameter (reference height)
h = 0.0
# Code to generate the data
n = 200
alpha = 0.75 * mt.pi
theta = [alpha + 2.0 * mt.pi * (float(k) / float(n)) for k in range(0, n + 1)]
xs = [1.0 * mt.cos(k) for k in theta]
ys = [1.0 * mt.sin(k) for k in theta]
zs = [abs(k - alpha - mt.pi) * rd.random() for k in theta]
# Code to convert data in 3D polygons
v = []
for k in range(0, len(xs) - 1):
x = [xs[k], xs[k+1], xs[k+1], xs[k]]
y = [ys[k], ys[k+1], ys[k+1], ys[k]]
z = [zs[k], zs[k+1], h, h]
#list is necessary in python 3/remove for python 2
v.append(list(zip(x, y, z)))
poly3dCollection = Poly3DCollection(v)
# Code to plot the 3D polygons
fig = pl.figure()
ax = Axes3D(fig)
ax.add_collection3d(poly3dCollection)
ax.set_xlim([min(xs), max(xs)])
ax.set_ylim([min(ys), max(ys)])
ax.set_zlim([min(zs), max(zs)])
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
pl.show()
It produces the following figure:
I hope this will help you.

Problem doing Polar plot of a antenna radiation function

I am trying to plot this radiation pattern figure
of the following equation:
I can make it partially:
%matplotlib inline
import matplotlib.ticker
import matplotlib.pyplot as plt
plt.style.use('seaborn-white')
import numpy as np
import pylab as pl
def gain_dip(theta, phi):
return 1.641*(np.cos(np.pi/2*np.cos(theta))/np.sin(theta))**2
theta = np.arange(-np.pi, np.pi,0.01)
# plot
ax = plt.subplot(111, polar=True)
# set zero west
ax.set_theta_zero_location('W')
ax.set_theta_direction('clockwise')
# let set an azimuth for example, pi
plt.plot(theta, gain_dip(theta, np.pi))
which plots the elevation angle (theta) and is similar to the elevation of the example. However, it has a line at 180 degrees angle that should not appear.
I would like to plot the azimuthal angle, phi, too, but when I try it gives error:
phi = np.arange(-np.pi, np.pi,0.01)
ax = plt.subplot(111, polar=True)
# set zero west
ax.set_theta_zero_location('W')
ax.set_theta_direction('clockwise')
plt.plot(phi, gain_dip(np.pi/2, phi))
ValueError: x and y must have same first dimension, but have shapes (629,) and (1,)
And, it is possible to plot both angles like in the first plot?

Heat map on unit sphere

I would like to plot a heat map on the unit sphere using the matplotlib library of python. There are several places where this question is discussed. Just like this: Heat Map half-sphere plot
I can do this partially. I can creat the sphere and the heatplot. I have coordinate matrices X,Y and Z, which have the same size. I have another variable of the same size as X, Y and Z, which contains scalars used to creat the heat map. However in case c contains scalars differ from zero in its first and last rows, just one polar cap will be colored but not the other. The code generates the above mentioned result is the next:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
#Creating the theta and phi values.
theta = np.linspace(0,np.pi,100,endpoint=True)
phi = np.linspace(0,np.pi*2,100,endpoint=True)
#Creating the coordinate grid for the unit sphere.
X = np.outer(np.sin(theta),np.cos(phi))
Y = np.outer(np.sin(theta),np.sin(phi))
Z = np.outer(np.cos(theta),np.ones(100))
#Creating a 2D matrix contains the values used to color the unit sphere.
c = np.zeros((100,100))
for i in range(100):
c[0,i] = 100
c[99,i] = 100
#Creat the plot.
fig = plt.figure()
ax = fig.add_subplot(111,projection='3d')
ax.set_axis_off()
ax.plot_surface(X,Y,Z, rstride=1, cstride=1, facecolors=cm.plasma(c/np.amax(c)), alpha=0.22, linewidth=1)
m = cm.ScalarMappable(cmap=cm.plasma)
m.set_array(c)
plt.colorbar(m)
#Show the plot.
plt.show()
The plot which was generated:
Could somebody help me what's going on here?
Thank you for your help in advance!
There are a number of small differences with your example but an
important one, namely the shape of the values array c.
As mentioned in another
answer the grid that
defines the surface is larger (by one in both dimensions) than the
grid that defines the value in each quadrangular patch, so that by
using a smaller array for c it is possible to choose correctly the
bands to color not only with respect to the beginnings of the c
array but also with respect to its ends, as I tried to demonstrate in
the following.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
# Creating the theta and phi values.
intervals = 8
ntheta = intervals
nphi = 2*intervals
theta = np.linspace(0, np.pi*1, ntheta+1)
phi = np.linspace(0, np.pi*2, nphi+1)
# Creating the coordinate grid for the unit sphere.
X = np.outer(np.sin(theta), np.cos(phi))
Y = np.outer(np.sin(theta), np.sin(phi))
Z = np.outer(np.cos(theta), np.ones(nphi+1))
# Creating a 2D array to be color-mapped on the unit sphere.
# {X, Y, Z}.shape → (ntheta+1, nphi+1) but c.shape → (ntheta, nphi)
c = np.zeros((ntheta, nphi)) + 0.4
# The poles are different
c[ :1, :] = 0.8
c[-1:, :] = 0.8
# as well as the zones across Greenwich
c[:, :1] = 0.0
c[:, -1:] = 0.0
# Creating the colormap thingies.
cm = mpl.cm.inferno
sm = mpl.cm.ScalarMappable(cmap=cm)
sm.set_array([])
# Creating the plot.
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=cm(c), alpha=0.3)
plt.colorbar(m)
# Showing the plot.
plt.show()
The values in the arrays define the edges of the grid. The color of the ith face is determined by the ith value in the color array. However, for n edges you only have n-1 faces, such that the last value is ignored.
E.g. if you have 4 grid values and 4 colors, the plot will have only the first three colors in the grid.
Thus a solution for the above would be to use a color array with one color less than gridpoints in each dimension.
c = np.zeros((99,99))
c[[0,98],:] = 100

Can you plot streamlines on Robinson projections?

I'm trying to plot streamlines on a global map with the Robinson projection, but basemap doesn't seem to like the projected co-ordinates. Of course, it works fine for a plain old cylindrical projection, which is regular in the x direction.
Here is an example:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
u = np.ones((21,21))
v = np.ones((21,21))
lats = np.arange(-90,91,9)
lons = np.arange(-180,181,18)
x,y = np.meshgrid(lons,lats)
# It works for Cylindrical
mp = Basemap(projection='cyl')
xx,yy = mp(x,y)
mp.streamplot(xx,yy,u,v)
mp.drawcoastlines()
plt.show()
# But not Robinson
mp = Basemap(projection='robin',lon_0=0)
xx, yy = mp(x, y)
mp.streamplot(xx,yy,u,v)
mp.drawcoastlines()
plt.show()
It complains about the x co-ordinates, raising:
ValueError: The rows of 'x' must be equal
So is it possible to plot streamlines on Robinson projections?
With the command xx,yy = mp(x,y) a coordinate transformation according to the particular projection is applied to your lon and lats. For most projections this will result in a distorsion of the gird point such that rows of x are no longer equal, hence the error: ValueError: The rows of 'x' must be equal. To fix this you need to re-grid your data, e.g. like this:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import numpy as np
import matplotlib as plt
u = np.ones((21,21))
v = np.ones((21,21))
lats = np.arange(-90,91,9)
lons = np.arange(-180,181,18)
x,y = np.meshgrid(lons,lats)
mp = Basemap(projection='robin',lon_0=0)
xx, yy = mp(x, y)
# generate a grid that is equally spaced in a plot with the current pojection
lons, lats, xxnew, yynew = mp.makegrid(21,21, returnxy=True)
# project the data onto the new grid
unew = plt.mlab.griddata(xx.flatten(), yy.flatten(),u.flatten(), xxnew, yynew ,interp='linear')
vnew = plt.mlab.griddata(xx.flatten(), yy.flatten(),v.flatten(), xxnew, yynew ,interp='linear')
mp.streamplot(xxnew,yynew,unew,vnew)
mp.drawcoastlines()
plt.show()

Heatmap with 3D data in matplotlib

I want to generate a heat map with my 3D data.
I have been able to plot trisurf using this data.
Can some one help me generate a heat map? I saw the online tutorials but they all seem quite complex for 3D. I found one on this website 'generating heatmap with scatter point in matplotlib but that is having only 2D data.
My code to generate trisurf is
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
# An array of radii
# Does not include radius r=0, this is to eliminate duplicate points
radii = np.linspace(0.125, 1.0, n_radii)
# An array of angles
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius
angles = np.repeat(angles[...,np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords
# (0, 0) is added here. There are no duplicate points in the (x, y) plane
x,y,z =np.loadtxt('output/flash_KR_endowment_duration_3D.dat',delimiter='\t',usecols=(0,1,2),unpack=True)
#x,y,z =np.loadtxt('output/disk_KR_endowment_duration_3D.dat',delimiter='\t',usecols=(0,1,2),unpack=True)
fig = plt.figure()
ax = fig.gca(projection='3d')
#fig.suptitle(suptitle, fontsize=12, fontweight='bold')
#ax.set_title("Disk Kryder's Rate-Endowment-Duration Plot",fontsize=12)
ax.set_title("Flash Kryder's Rate-Endowment-Duration Plot",fontsize=12)
ax.set_xlabel("Kryder's rate")
ax.set_ylabel("Duration")
ax.set_zlabel("Endowment")
surf = ax.plot_trisurf(x, y, z, cmap=cm.jet, linewidth=0.2)
fig.colorbar(surf, shrink=.7, aspect=20)
plt.show()
Data is 3 column. say X,Y,Z. I have tried 3D scatter plot with color. But I am looking for heatmap.
If you only "want to use 3rd dimension for coloring", you can do it like this:
import pandas as pd
import numpy as np
import plotly.plotly as plotly
from plotly.graph_objs import Data, Heatmap
plotly.sign_in("username", "api_key") # this is annoying but you can get one after registering - free
# generate tridimentional data
pp = pd.Panel(np.random.rand(20, 20, 20))
# crunch (sum, average...) data along one axis
crunch = pp.sum(axis=0)
# now plot with plot.ly or matplotlib as you wish
data = Data([Heatmap(z=np.array(crunch))])
plotly.image.save_as(data, "filename.pdf")
Result - heatmap with 3rd variable of 3D data as colour:
Additionally, you can plot for each combination of axis with a loop:
## Plot
# for each axis, sum data along axis, plot heatmap
# dict is axis:[x,y,z], where z is a count of that variable
desc = {0 : ["ax1", "ax2", "ax3"], 1 : ["ax1", "ax2", "ax3"], 2 : ["ax1", "ax2", "ax3"]}
for axis in xrange(3):
# crunch (sum) data along one axis
crunch = pp.sum(axis=axis)
# now let's plot
data = Data([Heatmap(
z=np.array(crunch),
x=crunch.columns,
y=crunch.index)]
)
plotly.image.save_as(data,
"heatmap_{0}_vs_{1}_count_of_{2}".format(desc[axis][0], desc[axis][1], desc[axis][2])
)

Categories

Resources