This is a code I found and slightly modified. How can I scale the color from the origin and set the axes from the origin for the visualization? I've tried to find information but most of it is for 2d plots.
Here I have added two arrays for theta and phi at intervals of 45 degrees and an array of random numbers representing the power of the signal. The plot works but the signals and intervals are not quite correct. My goal here is to just add the axes from the origin and scale the color from the origin.
import pandas as pd
import numpy as np
import scipy as sci
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as Axes3D
from matplotlib import cm, colors
from array import *
import random
#theta
vals_theta = array('i',[0,0,0,0,0,0,0,0,0,45,45,45,45,45,45,45,45,45,90,90,90,
90,90,90,90,90,90,135,135,135,135,135,135,135,135,135,
180,180,180,180,180,180,180,180,180])
#phi
vals_phi = array('i',[0,45,90,135,180,225,270,315,360,
0,45,90,135,180,225,270,315,360,
0,45,90,135,180,225,270,315,360,
0,45,90,135,180,225,270,315,360,
0,45,90,135,180,225,270,315,360])
#random numbers simulating the power data
vals_power = np.random.uniform(low=-7.2E-21, high=7.2E-21, size=(45,))
theta1d = vals_theta
theta1d = np.array(theta1d);
theta2d = theta1d.reshape([5,9])
phi1d = vals_phi
phi1d = np.array(phi1d);
phi2d = phi1d.reshape([5,9])
power1d = vals_power
power1d = np.array(power1d);
power2d = power1d.reshape([5,9])
THETA = np.deg2rad(theta2d)
PHI = np.deg2rad(phi2d)
R = power2d
Rmax = np.max(R)
X = R * np.sin(THETA) * np.cos(PHI)
Y = R * np.sin(THETA) * np.sin(PHI)
Z = R * np.cos(THETA)
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
ax.grid(True)
ax.axis('on')
ax.set_xticks([])
ax.set_yticks([])
ax.set_zticks([])
N = R / Rmax
ax.plot_surface(
X, Y, Z, rstride=1, cstride=1, cmap=plt.get_cmap('jet'),
linewidth=0, antialiased=False, alpha=0.5, zorder = 0.5)
ax.set_title('Spherical 3D Plot', fontsize=20)
m = cm.ScalarMappable(cmap=cm.jet)
m.set_array(R)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
m = cm.ScalarMappable(cmap=cm.jet)
m.set_array(R)
fig.colorbar(m, shrink=0.8);
ax.view_init(azim=300, elev = 30)
# Add Spherical Grid
phi ,theta = np.linspace(0, 2 * np.pi, 40), np.linspace(0, np.pi, 40)
PHI, THETA = np.meshgrid(phi,theta)
R = Rmax
X = R * np.sin(THETA) * np.cos(PHI)
Y = R * np.sin(THETA) * np.sin(PHI)
Z = R * np.cos(THETA)
ax.plot_wireframe(X, Y, Z, linewidth=0.5, rstride=3, cstride=3)
print(theta1d)
print(theta2d)
print(power2d)
plt.show()
Trying to get a result approximate to this

This is building off of Andrea's excellent answer, with a couple of additions that should be helpful for real-world data which may have fairly wide spacing between points. When I first plotted something with 45 degree spacing it looked like this:

There are two obvious issues:
Issue 1 can be improved by doing a linear interpolation of the data so that each face is divided into multiple sections that can have different colours.
Issue 2 happens because of the way face colours are assigned. Imagine a 3x3 grid of points on a 2D plane, with each point having a value. When you draw surfaces there will only be 2x2 faces, so the last row and column of values get thrown away, and the colour of each face is determined by only one corner of the face. What we really want is the value at the center of each face. We can estimate this by taking the average of the four corner values and using that to assign the colour.
Computationally this ends up being similar to the interpolation for issue 1, so I used the same function "interp_array" for both. I'm not much of a Python programmer so there's probably a more efficient way to do it, but it gets the job done.
Here is the plot with Issue 2 fixed but no interpolation. The symmetry is fixed but only 2 colours are used, because the faces are spaced equally from the origin.

And here is the final plot with 8x interpolation between the points. Now it's closer to the kind of continuous-colour plot that you would see in commercial antenna measurement software.

import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as Axes3D
from matplotlib import cm, colors
def interp_array(N1): # add interpolated rows and columns to array
N2 = np.empty([int(N1.shape[0]), int(2*N1.shape[1] - 1)]) # insert interpolated columns
N2[:, 0] = N1[:, 0] # original column
for k in range(N1.shape[1] - 1): # loop through columns
N2[:, 2*k+1] = np.mean(N1[:, [k, k + 1]], axis=1) # interpolated column
N2[:, 2*k+2] = N1[:, k+1] # original column
N3 = np.empty([int(2*N2.shape[0]-1), int(N2.shape[1])]) # insert interpolated columns
N3[0] = N2[0] # original row
for k in range(N2.shape[0] - 1): # loop through rows
N3[2*k+1] = np.mean(N2[[k, k + 1]], axis=0) # interpolated row
N3[2*k+2] = N2[k+1] # original row
return N3
vals_theta = np.arange(0,181,45)
vals_phi = np.arange(0,361,45)
vals_phi, vals_theta = np.meshgrid(vals_phi, vals_theta)
THETA = np.deg2rad(vals_theta)
PHI = np.deg2rad(vals_phi)
# simulate the power data
R = abs(np.cos(PHI)*np.sin(THETA)) # 2 lobes (front and back)
interp_factor = 3 # 0 = no interpolation, 1 = 2x the points, 2 = 4x the points, 3 = 8x, etc
X = R * np.sin(THETA) * np.cos(PHI)
Y = R * np.sin(THETA) * np.sin(PHI)
Z = R * np.cos(THETA)
for counter in range(interp_factor): # Interpolate between points to increase number of faces
X = interp_array(X)
Y = interp_array(Y)
Z = interp_array(Z)
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
ax.grid(True)
ax.axis('on')
ax.set_xticks([])
ax.set_yticks([])
ax.set_zticks([])
N = np.sqrt(X**2 + Y**2 + Z**2)
Rmax = np.max(N)
N = N/Rmax
axes_length = 1.5
ax.plot([0, axes_length*Rmax], [0, 0], [0, 0], linewidth=2, color='red')
ax.plot([0, 0], [0, axes_length*Rmax], [0, 0], linewidth=2, color='green')
ax.plot([0, 0], [0, 0], [0, axes_length*Rmax], linewidth=2, color='blue')
# Find middle points between values for face colours
N = interp_array(N)[1::2,1::2]
mycol = cm.jet(N)
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=mycol, linewidth=0.5, antialiased=True, shade=False) # , alpha=0.5, zorder = 0.5)
ax.set_xlim([-axes_length*Rmax, axes_length*Rmax])
ax.set_ylim([-axes_length*Rmax, axes_length*Rmax])
ax.set_zlim([-axes_length*Rmax, axes_length*Rmax])
m = cm.ScalarMappable(cmap=cm.jet)
m.set_array(R)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
fig.colorbar(m, shrink=0.8)
ax.view_init(azim=300, elev=30)
plt.show()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With