4D plots with single colour describing the 4th dimension and connected by line

67 Views Asked by At

I have been searching the internet for the past couple of days and I have not seen any solution. I will be grateful if someone can please have a look.

I am trying to plot a 4D plot to show wind direction variation at a given longitude, latitude, and altitude.

I see a jump in my graph when the object moves from one altitude to the other. Is it possible to connect the points with a line? to indicate that the is a movement.

The sample code is shown below:

import numpy as np
from matplotlib import pyplot as plt

lon = np.array([278.6695, 278.67,278.672265 ])
lat = np.array([48.476151, 48.472578621119, 48.45994295 ])
Z  = np.array([20000, 26502.51477,26501.65171])
distance = np.array([72.63856248, 70, 60 ])

fig = plt.figure(6)
ax = fig.add_subplot(111, projection='3d')
img = ax.scatter(lon, lat, Z, c=distance, cmap='inferno', alpha=1)
fig.colorbar(img)

ax.set_xlabel('longitude [Deg]')
ax.set_ylabel('latitude [Deg]')
ax.set_zlabel('Altitude [Km]')


plt.show()

My result

My result when I plot my whole data Result with multiple data

Secondly, I would like to show the distance using just one colour (i.e. Black ) instead of using multiple colours. My end goal is to have a plot like this

Desired plot

1

There are 1 best solutions below

6
trent On BEST ANSWER

This answer shows how to create the final plot that you asked for. Both requests (single color and connected line) are very possible. A Google search of your desired plot image shows that it was originally an animation, which is something else that I have included.

Answer

Use ax.plot(...) instead of ax.scatter(...) in order to connect the points with a line. Then you can add a few other features to make the output look more like your end goal.

Example Output

Here is a figure showing your end goal. The code to reproduce the figure is added below.

static version of the hodograph

It is also possible to create an animation of the plot over time. Note that the color of the lines changes as the distance from the start position increases. This feature is easily disabled.

animated hodograph

Code

This is some setup code to be used for both static and animated figures.

# necessary imports
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.art3d as art3d
from matplotlib.animation import FuncAnimation
from matplotlib.patches import Circle

# creating dummy data for two objects
lon = np.sort(277.0 + np.random.rand(2,100)*2)
lat = np.sort(48 + np.random.rand(2,100))
Z  = np.sort(20000 + np.random.rand(2,100)*8000)
lat[1] -= 2*(-lat[1][0] + lat[1]) # moving the lines in different directions

This is for the static version of the figure.

# creating the main figure and setting boundaries (change these as needed)
fig = plt.figure(6)
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim(276, 280)
ax.set_ylim(46, 50)
ax.set_zlim(np.min(Z), 30000)

ax.set_xlabel('longitude [Deg]')
ax.set_ylabel('latitude [Deg]')
ax.set_zlabel('Altitude [Km]')
ax.zaxis.set_pane_color((0.36, 0.7, 0.42, 0.75)) # making the 'ground' green

# plotting data from object 1
# line plot for the path of the object
line = ax.plot(lon[0],lat[0],Z[0], c='k')
# plotting a vertical line from the ground to the final position of the object
vertLine = ax.plot([lon[0][-1],lon[0][-1]],[lat[0][-1],lat[0][-1]],[np.min(Z),Z[0][-1]], c='k')
# plotting a dot at the final postion of the object
dot = ax.plot(lon[0][-1], lat[0][-1], Z[0][-1], linestyle="", marker="o", c='k')

# plotting data from object 2 (see above for details)
line1 = ax.plot(lon[1],lat[1],Z[1], c='r', alpha=1)
vertLine1 = ax.plot([lon[1][-1],lon[1][-1]],[lat[1][-1],lat[1][-1]],[np.min(Z),Z[1][-1]], c='r', alpha=1)
dot1 = ax.plot(lon[1][-1], lat[1][-1], Z[1][-1], linestyle="", marker="o", c='r', alpha=1)

# adding green reference circle to the 'ground'
p = Circle((278,48.5),1, alpha=0.75, edgecolor=(0.3, 0.6, 0.39, 0.75), fill=False)
ax.add_patch(p)
art3d.pathpatch_2d_to_3d(p, z=np.min(Z), zdir="z")
ax.text(278,48.5,np.min(Z),"x", color=(0.3, 0.6, 0.39, 0.75))

# creating a new subplot for top down view
ax2 = fig.add_subplot(331)
ax2.set_xlim(276, 280)
ax2.set_ylim(46, 50)
ax2.set_aspect('equal')

# adding reference circle and background color
ax2.set_facecolor((0.36, 0.7, 0.42, 0.75))
p2 = Circle((278,48.5),1, alpha=0.75, facecolor=(0.3, 0.6, 0.39, 0.75))
ax2.add_patch(p2)

# plotting path lines and final position dots
ax2.plot(lon[0],lat[0], c='k')
ax2.plot(lon[1],lat[1], c='r')
ax2.scatter(lon[0][-1], lat[0][-1], c='k',s=20)
ax2.scatter(lon[1][-1], lat[1][-1], c='r',s=20)

# Show the plot!
plt.show()

This is the code to create an animated version of the plot over time.

# create figure/axis with appropriate params
fig = plt.figure(6)
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim(276, 280)
ax.set_ylim(47, 50)
ax.set_zlim(18000, 30000)

ax.set_xlabel('longitude [Deg]')
ax.set_ylabel('latitude [Deg]')
ax.set_zlabel('Altitude [Km]')
ax.zaxis.set_pane_color((0.36, 0.7, 0.42, 0.75))

# create all components of the first object
line, = ax.plot(lon[0][:2],lat[0][:2],Z[0][:2], c='k', alpha=1)
vertLine, = ax.plot([lon[0][1],lon[0][1]],[lat[0][1],lat[0][1]],[np.min(Z[0]),Z[0][1]], c='k', alpha=1)
dot, = ax.plot(lon[0][1], lat[0][1], Z[0][1], linestyle="", marker="o", c='k', alpha=1)

# create all components of the second object
line1, = ax.plot(lon[1][:2],lat[1][:2],Z[1][:2], c='r', alpha=1)
vertLine1, = ax.plot([lon[1][1],lon[1][1]],[lat[1][1],lat[1][1]],[np.min(Z[1]),Z[1][1]], c='r', alpha=1)
dot1, = ax.plot(lon[1][1], lat[1][1], Z[1][1], linestyle="", marker="o", c='r', alpha=1)

def animate(i, colUpdate, numFrs): # colUpdate (bool), numFrs (int)
    # update the data for the path lines
    line.set_data(lon[0][:(i+1)*2],lat[0][:(i+1)*2])
    line.set_3d_properties(Z[0][:(i+1)*2])
    line1.set_data(lon[1][:(i+1)*2],lat[1][:(i+1)*2])
    line1.set_3d_properties(Z[1][:(i+1)*2])
    
    # update the data for the vertical lines
    vertLine.set_data(np.array([lon[0][((i+1)*2)-1],lon[0][((i+1)*2)-1]]),np.array([lat[0][((i+1)*2)-1],lat[0][((i+1)*2)-1]]))
    vertLine.set_3d_properties([np.min(Z[0]),Z[0][((i+1)*2)-1]])
    vertLine1.set_data(np.array([lon[1][((i+1)*2)-1],lon[1][((i+1)*2)-1]]),np.array([lat[1][((i+1)*2)-1],lat[1][((i+1)*2)-1]]))
    vertLine1.set_3d_properties([np.min(Z[1]),Z[1][((i+1)*2)-1]])
    
    # update the data for the endpoint dots
    dot.set_data(lon[0][((i+1)*2)-1],lat[0][((i+1)*2)-1])
    dot.set_3d_properties(Z[0][((i+1)*2)-1])
    dot1.set_data(lon[1][((i+1)*2)-1],lat[1][((i+1)*2)-1])
    dot1.set_3d_properties(Z[1][((i+1)*2)-1])

    # update the colors, if necessary
    if colUpdate:
        co = np.array([(i/numFrs),0.0,0.0])
        co1 = np.array([0.0,0.0,(i/numFrs)])
    
        line.set_color(co)
        vertLine.set_color(co)
        dot.set_color(co)
        
        line1.set_color(co1)
        vertLine1.set_color(co1)
        dot1.set_color(co1)
    # return all updated objects
    return line, vertLine, dot, line1, vertLine1, dot1,

# set params and create the animation artist
updateCol = True
numFrames = int(len(Z[0]) /2)
anim = FuncAnimation(fig, animate, frames = numFrames,interval = 100, fargs=(updateCol,numFrames,), blit=False, repeat=False)

# Show the plot!
plt.show()

Edit

In order to fix the animation portion of the code to be compatible with Matplotlib 3.5.1, you must change the following section of code in the animate(...) function. Replace the following:

    # update the data for the endpoint dots
    dot.set_data(lon[0][((i+1)*2)-1],lat[0][((i+1)*2)-1])
    dot.set_3d_properties(Z[0][((i+1)*2)-1])
    dot1.set_data(lon[1][((i+1)*2)-1],lat[1][((i+1)*2)-1])
    dot1.set_3d_properties(Z[1][((i+1)*2)-1])

With:

    # update the data for the endpoint dots
    dot.set_data(lon[0][((i+1)*2)-1],lat[0][((i+1)*2)-1])
    dot.set_3d_properties([Z[0][((i+1)*2)-1]])
    dot1.set_data(lon[1][((i+1)*2)-1],lat[1][((i+1)*2)-1])
    dot1.set_3d_properties([Z[1][((i+1)*2)-1]])

All this does is change the input for the set_3d_properties(...) to list format, which is the new standard in Matplotlib 3.5.1. See this open issue for a description of the problem.

Does this help?