Creating a Surface of Revolution - python

I have a 3d plot of a disk, here is the code:
ri = 100
ra = 300
h=20
# input xy coordinates
xy = np.array([[ri,0],[ra,0],[ra,h],[ri,h],[ri,0]])
# radial component is x values of input
r = xy[:,0]
# angular component is one revolution of 30 steps
phi = np.linspace(0, 2*np.pi, 50)
# create grid
R,Phi = np.meshgrid(r,phi)
# transform to cartesian coordinates
X = R*np.cos(Phi)
Y = R*np.sin(Phi)
# Z values are y values, repeated 30 times
Z = np.tile(xy[:,1],len(Y)).reshape(Y.shape)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='3d')
ax.set_zlim(0,200)
ax.plot_surface(X, Y, Z, alpha=0.5, color='grey', rstride=1, cstride=1)
I get this nice plot:
Further I have this plot:
The code is:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
arr = np.array([[100, 15],
[114.28, 17],
[128.57, 18],
[142.85, 19],
[157.13, 22],
[171.13, 24],
[185.69, 25],
[199.97, 27],
[214.25, 28],
[228.53, 30],
[242.81, 31],
[257.09, 35],
[271.37, 36],
[288.65, 37],
[300, 38]])
#interpolating between the single values of the arrays
new_x = np.concatenate([np.linspace(arr[i,0],arr[i+1,0], num=50)
for i in range(len(arr)-1)])
new_y = np.interp(new_x, arr[:,0], arr[:,1])
t=np.arange(700)
p = plt.scatter(new_x,new_y,c=t, cmap="jet")
#inserting colorbar
cax, _ = mpl.colorbar.make_axes(plt.gca(), shrink=0.8)
cbar = mpl.colorbar.ColorbarBase(cax, cmap='jet', label='testvalues',
norm=mpl.colors.Normalize(15, 40))
plt.show()
Now my question:
Is there a way to plot this 2d graph into my 3d environment? Further is it possible to create a surface out of this line (points) by rotating them around the middlepoint ? I tried it the same way like I did it with my disk but I failed because I think I need a closed contour ? Here is a picture to understand better what I want:

I'm not sure how you want to include your 2d plot, so here's how you do it as a surface of revolution.
Your new_x corresponds to radial distance, new_y corresponds to height. So we need to generate an array of angles for which to generate the "cone":
from matplotlib import cm
tmp_phi = np.linspace(0,2*np.pi,50)[:,None] # angle data
linesurf_x = new_x*np.cos(tmp_phi)
linesurf_y = new_x*np.sin(tmp_phi)
linesurf_z = np.broadcast_to(new_y, linesurf_x.shape)
linesurf_c = np.broadcast_to(t, linesurf_x.shape) # color according to t
colors = cm.jet(linesurf_c/linesurf_c.max()) # grab actual colors for the surface
ax.plot_surface(linesurf_x, linesurf_y, linesurf_z, facecolors=colors,
rstride=1, cstride=1)
Result:

Related

3D barplot in matplotlib, with scaled gradient colormap corresponding to a 4th dimension (range of values)

I am trying to create a 3D barplot using matplotlib in python, and apply a colormap which is tied some data (4th dimension) which is not explicitly plotted. I think what makes this even more complicated is that I want this 4th dimension to be a range of values as opposed to a single value.
So far I have managed to create the 3D bar plot with a colormap tied to the z-dimension thanks primarily to this post how to plot gradient fill on the 3d bars in matplotlib. The code can be found below.
import numpy as np
import glob,os
from matplotlib import pyplot as plt
import matplotlib.colors as cl
import matplotlib.cm as cm
from mpl_toolkits.mplot3d import Axes3D
os.chdir('./')
# axis details for the bar plot
x = ['1', '2', '3', '4', '5'] # labels
x_tick_locks = np.arange(0.1, len(x) + 0.1, 1)
x_axis = np.arange(len(x))
y = ['A', 'B']
y_tick_locks = np.arange(-0.1, len(y) - 0.1, 1)
y_axis = np.arange(len(y))
x_axis, y_axis = np.meshgrid(x_axis, y_axis)
x_axis = x_axis.flatten()
y_axis = y_axis.flatten()
x_data_final = np.ones(len(x) * len(y)) * 0.5
y_data_final = np.ones(len(x) * len(y)) * 0.5
z_axis = np.zeros(len(x)*len(y))
z_data_final = [[30, 10, 15, 20, 25], [10, 15, 15, 28, 40]]
values_min = [[5, 1, 6, 8, 3], [2, 1, 3, 9, 4]]
values_max = [[20, 45, 11, 60, 30], [11, 28, 6, 30, 40]]
cmap_max = max(values_max)
cmap_min = min(values_min)
############################### FOR 3D SCALED GRADIENT BARS ###############################
def make_bar(ax, x0=0, y0=0, width = 0.5, height=1 , cmap="plasma",
norm=cl.Normalize(vmin=0, vmax=1), **kwargs ):
# Make data
u = np.linspace(0, 2*np.pi, 4+1)+np.pi/4.
v_ = np.linspace(np.pi/4., 3./4*np.pi, 100)
v = np.linspace(0, np.pi, len(v_)+2 )
v[0] = 0 ; v[-1] = np.pi; v[1:-1] = v_
#print(u)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
xthr = np.sin(np.pi/4.)**2 ; zthr = np.sin(np.pi/4.)
x[x > xthr] = xthr; x[x < -xthr] = -xthr
y[y > xthr] = xthr; y[y < -xthr] = -xthr
z[z > zthr] = zthr ; z[z < -zthr] = -zthr
x *= 1./xthr*width; y *= 1./xthr*width
z += zthr
z *= height/(2.*zthr)
#translate
x += x0; y += y0
#plot
ax.plot_surface(x, y, z, cmap=cmap, norm=norm, **kwargs)
def make_bars(ax, x, y, height, width=1):
widths = np.array(width)*np.ones_like(x)
x = np.array(x).flatten()
y = np.array(y).flatten()
h = np.array(height).flatten()
w = np.array(widths).flatten()
norm = cl.Normalize(vmin=0, vmax=h.max())
for i in range(len(x.flatten())):
make_bar(ax, x0=x[i], y0=y[i], width = w[i] , height=h[i], norm=norm)
############################### FOR 3D SCALED GRADIENT BARS ###############################
# Creating graph surface
fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(111, projection= Axes3D.name)
ax.azim = 50
ax.dist = 10
ax.elev = 30
ax.invert_xaxis()
ax.set_box_aspect((1, 0.5, 1))
ax.zaxis.labelpad=7
ax.text(0.9, 2.2, 0, 'Group', 'x')
ax.text(-2, 0.7, 0, 'Class', 'y')
ax.set_xticks(x_tick_locks)
ax.set_xticklabels(x, ha='left')
ax.tick_params(axis='x', which='major', pad=-2)
ax.set_yticks(y_tick_locks)
ax.set_yticklabels(y, ha='right', rotation=30)
ax.tick_params(axis='y', which='major', pad=-5)
ax.set_zlabel('Number')
make_bars(ax, x_axis, y_axis, z_data_final, width=0.2, )
fig.colorbar(plt.cm.ScalarMappable(cmap = 'plasma'), ax = ax, shrink=0.8)
#plt.tight_layout() # doesn't seem to work properly for 3d plots?
plt.show()
As I mentioned, I don't want the colormap to be tied to the z-axis but rather a 4th dimension, which is a range. In other words, I want the colours of the colormap to range from cmap_min to cmap_max (so min is 1 and max is 60), then for the bar plot with a z_data_final entry of 30 for example, its colours should correspond with the range of 5 to 20.
Some other posts seem to provide a solution for a single 4th dimensional value, i.e. (python) plot 3d surface with colormap as 4th dimension, function of x,y,z or How to make a 4d plot using Python with matplotlib however I wasn't able to find anything specific to bar plots with a range of values as your 4th dimensional data.
I would appreciate any guidance in this matter, thanks in advance.
This is the 3D bar plot with colormap tied to the z-dimension

How to colour a variable on basis of highest and lowest or at some cut off value for 3d Bar graph in python

I want to give different gradient colour for z i.e Numeric variable in my 3D Bar Graph on the basis of some cut of value or gradient for lowest to highest value.I want to put condition say if dz is >=50 then green colour bar else red colour ba. Attached the code, Please share if there's any solution for this.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection = "3d")
ax.set_xlabel("Cumulative HH's")
ax.set_ylabel("Index")
ax.set_zlabel("# Observations")
xpos = [1,2,3,4,5,6,7,8,9,10,11,12]#
ypos = [2,4,6,8,10,12,14,16,18,20,22,24]#
zpos = np.zeros(12)
dx = np.ones(12)
dy = np.ones(12)
dz = [100,3,47,35,8,59,24,19,89,60,11,25]
colors=['pink']
ax.bar3d(xpos,ypos,zpos,dx,dy,dz,color=colors)
The color= parameter to bar3d can be a list of colors with one entry per bar. Such a list can be built using a colormap.
Here is an example that colors the bars using a smooth range from green for the highest and red for the lowest. Changing the colormap to cmap = plt.cm.get_cmap('RdYlGn', 2) would color all the bars higher than the mean in green and the rest in red. To set the split condition exactly at 50, you can change the norm to norm = mcolors.Normalize(0, 100).
If only a few different colors are needed, the easiest is to forget about cmap and norm and just use:
colors = ['limegreen' if u > 50 else 'crimson' for u in dz]
Here is a complete example:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.set_xlabel("Cumulative HH's")
ax.set_ylabel("Index")
ax.set_zlabel("# Observations")
xpos = np.arange(1, 13)
ypos = np.arange(2, 26, 2)
zpos = np.zeros(12)
dx = np.ones(12)
dy = np.ones(12)
dz = [100, 3, 47, 35, 8, 59, 24, 19, 89, 60, 11, 25]
cmap = plt.cm.get_cmap('RdYlGn')
norm = mcolors.Normalize(min(dz), max(dz))
colors = [cmap(norm(u)) for u in dz]
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=colors)
plt.show()
At the left an example with a range of colors, at the right an example with only 2 colors:

Matplotlib center/align ticks in imshow plot

I have been trying to center the x and y ticks of my imshow but without success.
The desired yticks should be: [ 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000] and xticks: [ 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55] but aligned/centered. E.g. line 1 should have the 100 value exactly in the middle of the line space (middle of the yellow box/pixel).
import numpy as np
import matplotlib.pyplot as plt
X = np.random.rand(10,11)
plt.figure(dpi=130)
plt.imshow(X, cmap = 'jet', interpolation=None, extent=[5,55,1000,100], aspect='auto')
Here, the values 5 does not appear at all in the x axis.
I have also tried the following, x axis if fine but not the y axis
plt.figure(dpi=130)
X = np.random.rand(10,11)
plt.imshow(X, cmap = 'jet', interpolation=None, extent=[2.5,57.5,1000,100], aspect='auto')
ax = plt.gca()
xticks = cluster_space
yticks = space_segment
ax.set_xticks(xticks)
ax.set_yticks(yticks)
In general, to have the pixels centered, you need to set the extent to range from the lowest pixel coordinate minus half the pixel width to the highest pixel coordinate plus half the pixel width.
import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
X = np.random.rand(10,11)
plt.figure()
centers = [5,55,1000,100]
dx, = np.diff(centers[:2])/(X.shape[1]-1)
dy, = -np.diff(centers[2:])/(X.shape[0]-1)
extent = [centers[0]-dx/2, centers[1]+dx/2, centers[2]+dy/2, centers[3]-dy/2]
plt.imshow(X, cmap = 'jet', interpolation=None, extent=extent, aspect='auto')
plt.xticks(np.arange(centers[0], centers[1]+dx, dx))
plt.yticks(np.arange(centers[3], centers[2]+dy, dy))
plt.show()

matplotlib colorbar boundaries do not implemented

I am trying to create several plots all with the same colorbar limits in a loop.
I set the limits of the contour plot with map.contourf(x, y, U_10m, vmin=0, vmax=25) and this seems to give consistent colour scales for each plot. However, when I use cbar = plt.colorbar(boundaries=np.linspace(0,1,25), ticks=[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24]) # sets all cbar to same limits each plot does not have the same colorbar limits (examples of two plots with different colorbars and code below).
from netCDF4 import Dataset as NetCDFFile
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
def wrf_tseries_contour_plotter (
ncfile, time_ind, lowerllat, upperrlat, lowerllon, upperrlon, output_dir):
'''
EDITED FROM http://www.atmos.washington.edu/~ovens/wrfwinds.html
'''
print 'timestep:', + time_ind
#which figure is being generated 0 = 00:00, 144 = 23:50
nc = NetCDFFile(ncfile, 'r')
#
# get the actual longitudes, latitudes, and corners
lons = nc.variables['XLONG'][time_ind]
lats = nc.variables['XLAT'][time_ind]
#get the u10 to plot as a contour instead of t2m
U10_raw = nc.variables['U10'][time_ind] #61 is the index for 10:00am
V10_raw = nc.variables['V10'][time_ind]
#bodge to calculate U from U and V (u10 = sqrt(u^2+v^2))
v2 = np.square(V10_raw)
u2 = np.square(U10_raw)
U_10m = np.sqrt(u2 + v2)
# Make map
map = Basemap(projection='cyl',llcrnrlat=lowerllat,urcrnrlat=upperrlat,
llcrnrlon=lowerllon,urcrnrlon=upperrlon,
resolution='h')
# lllat, urlat,lllon, urlon set outside of f(x) lower left and
# upper right lat/lon for basemap axis limits
x, y = map(lons[:,:], lats[:,:])
map.contourf(x, y, U_10m, vmin=0, vmax=25)
map.drawcoastlines(linewidth = 0.5, color = '0.15')
#thinner lines for larger scale map
#plt.clim(0, 25) #added
cbar = plt.colorbar(boundaries=np.linspace(0,1,25), ticks=[0, 2, 4, 6,
8, 10, 12, 14, 16, 18, 20, 22, 24]) # sets all cbar to same limits
cbar.set_label('10m U (m/s)', size=12)
cbar.ax.set_yticklabels([0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24])
#cbar.set_clim(0, 25)
time_str = str(time_ind)
plt.title('gust 20070724' + '_' + time_str)
fig_name = '\gust20070724_'+ time_str + '.png'
plt.savefig(output_dir + fig_name)
plt.close()
#set inputs for wrf_tseries_contour_plotter(ncfile, time_ind, lllat, urlat,
lllon, urlon, output_dir)
ncfile = 'E:\WRFout_UK2Fino\wrfout_d03_2007-07-24_00%3A00%3A00'
tlist = np.arange(0,145)
#set the lower left/upper right lat/lon for axis limits on the maps
lowerllat=48
upperrlat=63
lowerllon=-10
upperrlon=25
#set output directory for figures
output_dir = '''C:\cbar_test'''
for time_ind in tlist:
wrf_tseries_contour_plotter(ncfile, time_ind, lowerllat, upperrlat,
lowerllon, upperrlon, output_dir)
You have to use vmin and vmax values to set boundaries of a colorbar like in this example:
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
# test data
x = np.linspace(0,15,100)
X,Y = np.meshgrid(x,x)
SPD1 = np.sqrt(X*X + Y*Y)
SPD2 = SPD1 * 1.3
fig = plt.figure()
# implement boundaries of colorbar and it ticks
vmin, vmax = 0, 26
levels = np.linspace(vmin,vmax,14)
# 1st subplot
ax1 = fig.add_subplot(221)
# Set contour levels and limits
CF1 = ax1.contourf(X, Y, SPD1, levels=levels, vmax=vmax, vmin=vmin)
cbar = plt.colorbar(CF1)
cbar.set_label('10m U (m/s)', size=12)
#2nd subplot
ax1 = fig.add_subplot(222)
CF1 = ax1.contourf(X, Y, SPD2, levels=levels, vmax=vmax, vmin=vmin)
cbar = plt.colorbar(CF1)
cbar.set_label('10m U (m/s)', size=12)
plt.tight_layout()
plt.show()
However you have to select vmin, vmax correctly because of if your values are outside boundaries of colorbar they will not shown (right upper corner of 2nd subplot).

Python matplotlib divide scatterplot into categories based on slope

I have a list of x and a list of y values. I'd like to construct a scatterplot in Matplotlib and divide the dots into five categories based on their x and y coordinates, like in the image below:
angles = [0, 18, 36, 54, 72, 90]
colors = ['r','g','b','c']
x = [....]
y = [....]
All of the points in the divided category will be the same color. It would also be great to have a legend for the categories. I am new to Matplotlib and Python, does anyone know how I can approach this?
Here's a working example which will give you a little idea to get started:
from matplotlib import pyplot as plt
from matplotlib.lines import Line2D
import math
import random
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
w, h = 7, 5
a = [[random.random() * w, random.random() * h] for i in range(100)]
plt.plot(*zip(*a), marker='o', color='r', ls='')
for deg in [18, 36, 54]:
r = 10
line = Line2D([0, r * math.cos(math.radians(deg))],
[0, r * math.sin(math.radians(deg))],
linewidth=1, linestyle="-", color="green")
ax.add_line(line)
ax.set_xlim(0, w)
ax.set_ylim(0, h)
plt.legend()
plt.show()

Categories