3

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

Zephyr
  • 11,891
  • 53
  • 45
  • 80
okrus
  • 81
  • 1
  • 1
  • 8
  • Origin* haha..... – okrus Feb 22 '19 at 12:18
  • Have you see some other's work for example https://medium.com/@johngrant/antenna-arrays-and-python-plotting-with-pyplot-ae895236396a or tried Pylayer tool example at https://pylayers.github.io/pylayers/notebook/2-AP/Antenna.html? – Cloud Cho Apr 16 '20 at 07:28
  • I found out your radiation pattern isn't symmetric...would you explain this? – Cloud Cho Apr 24 '20 at 00:56

2 Answers2

2

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:

Initial Plot

There are two obvious issues:

  1. The faces are very large and only have a single colour, even though they span a wide range of values.
  2. The shape is symmetric about the origin, but the colours of the faces are not symmetric.

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.

Symmetry Fix

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.

Interpolation Added

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()
AMTK
  • 183
  • 1
  • 9
1

You can add axes lines of unitary length with:

ax.plot([0, 1], [0, 0], [0, 0], linewidth=2, color = 'red')
ax.plot([0, 0], [0, 1], [0, 0], linewidth=2, color = 'green')
ax.plot([0, 0], [0, 0], [0, 1], linewidth=2, color = 'blue')

Regarding the color of the surface, you need to define an expression that represents the distance from the origin, then use this expression to create your colormap and pass it to the facecolors parameter of ax.plot_surface as here:

dist = np.sqrt(X**2 + Y**2 + Z**2)
dist_max = np.max(dist)
my_col = cm.jet(dist/dist_max)

surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=my_col, linewidth=0, antialiased=False)

The complete code:

from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = 8*np.sin(R)
dist = np.sqrt(X**2 + Y**2 + Z**2)
dist_max = np.max(dist)
my_col = cm.jet(dist/dist_max)

axes_length = 1.5
ax.plot([0, axes_length*dist_max], [0, 0], [0, 0], linewidth=2, color = 'red')
ax.plot([0, 0], [0, axes_length*dist_max], [0, 0], linewidth=2, color = 'green')
ax.plot([0, 0], [0, 0], [0, axes_length*dist_max], linewidth=2, color = 'blue')

surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=my_col,
        linewidth=0, antialiased=False)

ax.set_xlim([-axes_length*dist_max, axes_length*dist_max])
ax.set_ylim([-axes_length*dist_max, axes_length*dist_max])
ax.set_zlim([-axes_length*dist_max, axes_length*dist_max])

plt.show()

which gives me this result:

enter image description here

As you can see, the color of the surface goes from blue near the origin to the red far from it. It should not be difficult to apply this code to your data.

Zephyr
  • 11,891
  • 53
  • 45
  • 80
  • Thanks so much for this! Great timing as I just happened to be working on the same problem with the same sample code. The one remaining problem is that with a more coarse data set like you would get from a real antenna measurement system, the colour of each surface is fixed based on a single point. I guess I need to interpolate between the points somehow? Here is my output: https://i.imgur.com/UcvydvX.png – AMTK Jul 21 '20 at 17:37
  • I would say yes, but it is not my field: the question you asked is specific to telecommunications engineering – Zephyr Jul 21 '20 at 18:47
  • Unfortunately I am a telecommunications engineer haha. But I would say it's not an engineering issue, just a question of what can be done in python to display the data more nicely. Actually it looks pretty good with slightly closer spacing (22.5 degree increments), plot here: https://i.imgur.com/zWHmDAE.png It just annoys me that it's not symmetric when it obviously should be. It seems like it's drawing it from one side to the other and choosing the colour based on the first point drawn. I'll try to dig in to how it's assigning the colours and see if it can at least be made symmetric. – AMTK Jul 21 '20 at 19:54
  • 1
    I figured it out! It's basically an off-by-one error, it happens because for example if you have a 3x3 grid of points, it maps to a 2x2 grid of faces, so the last row and last column get thrown away. I solved it by creating a new array for the color map where all the values are halfway in between those of the original dist array. I'll post my code as a new answer soon, but I want to implement linear interpolation to increase the number of faces. – AMTK Jul 22 '20 at 16:24