I have a three columns catalogue of data and I would like to make a 3D plot of them plus the projection of each axis as a projected contour in the the plane of the other two axises. So far I could make the 3D plot using matplotlib which still doesn't show anything from the properties of the data.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from numpy import *
data=loadtxt('test.cat')
X=data[:,0]
Y=data[:,1]
Z=data[:,2]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X, Y, Z, c='r', marker='.')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
How could I plot the projection of the data in each plane with colorbar as well?
hmm, indeed, difficult data to display. Maybe creating some slices along one axis and creating certain number 2D plots would be best. However 3D plots are fancy. I played a bit with the data resulting in one 3D plot as you did and a separate plot with the projections.
The colors of the points are according the missing axis
Added transparency to give an idea of density
Kept axes of both plots the same
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
data = np.loadtxt('test.cat', skiprows=1)
X=data[:,0]
Y=data[:,1]
Z=data[:,2]
plt.figure()
ax1 = plt.subplot(111, projection='3d')
ax1.scatter(X, Y, Z, c='b', marker='.', alpha=0.2)
ax1.set_xlabel('X - axis')
ax1.set_ylabel('Y - axis')
ax1.set_zlabel('Z - axis')
plt.figure()
ax2 = plt.subplot(111, projection='3d')
plt.hot()
cx = np.ones_like(X) * ax1.get_xlim3d()[0]
cy = np.ones_like(X) * ax1.get_ylim3d()[1]
cz = np.ones_like(Z) * ax1.get_zlim3d()[0]
ax2.scatter(X, Y, cz, c=Z, marker='.', lw=0, alpha=0.2)
ax2.scatter(X, cy, Z, c=-Y, marker='.', lw=0, alpha=0.2)
ax2.scatter(cx, Y, Z, c=X, marker='.', lw=0, alpha=0.2)
ax2.set_xlim3d(ax1.get_xlim3d())
ax2.set_ylim3d(ax1.get_ylim3d())
ax2.set_zlim3d(ax1.get_zlim3d())
ax2.set_xlabel('X - axis')
ax2.set_ylabel('Y - axis')
ax2.set_zlabel('Z - axis')
According to what you want to do you need to use the zdir parameter for the contour and contourf functions. Here an example:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = Axes3D(fig)
X = np.arange(-4, 4, 0.25)
Y = np.arange(-4, 4, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X ** 2 + Y ** 2)
Z = np.sin(R)
ax.contourf(X, Y, Z, zdir='x', offset=-4, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='x', offset=-4, colors='k')
ax.contourf(X, Y, Z, zdir='y', offset=4, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='y', offset=4, colors='k')
ax.contourf(X, Y, Z, zdir='z', offset=-1, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='z', offset=-1, colors='k')
plt.show()
With result:
Here's hoping I'm not shooting a mosquito with a [very ugly] cannon, I recently made a Binning_Array class for a school project and I think it might be able to help you:
import numpy as np
import matplotlib.pyplot as plt
import binning_array as ba
from mpl_toolkits.mplot3d import axes3d
data = np.loadtxt('test.cat')
X = data[:,0]
Y = data[:,1]
Z = data[:,2]
n_points = data.shape[0]
X_min = np.round(np.min(data[:,0])-0.5)
X_max = np.round(np.max(data[:,0])+0.5)
Y_min = np.round(np.min(data[:,1])-0.5)
Y_max = np.round(np.max(data[:,1])+0.5)
Z_min = np.round(np.min(data[:,2])-0.5)
Z_max = np.round(np.max(data[:,2])+0.5)
n_min_bins = 25
step = min([(X_max-X_min)/n_min_bins, (Y_max-Y_min)/n_min_bins, (Z_max-Z_min)/n_min_bins])
# Using three Binners
BinnerXY = ba.Binning_Array([[X_min, X_max, step],
[Y_min, Y_max, step]])
BinnerYZ = ba.Binning_Array([[Y_min, Y_max, step],
[Z_min, Z_max, step]])
BinnerXZ = ba.Binning_Array([[X_min, X_max, step],
[Z_min, Z_max, step]])
for point in data:
BinnerXY.add_value([point[0], point[1]])
BinnerXZ.add_value([point[0], point[2]])
BinnerYZ.add_value([point[1], point[2]])
fig = plt.figure()
ax = [fig.add_subplot(221, projection='3d'),
fig.add_subplot(222),
fig.add_subplot(223),
fig.add_subplot(224)]
# Plot 2D projections on the 3D graph
vmin = np.min([BinnerXZ.bin_min(), BinnerYZ.bin_min(), BinnerXY.bin_min()])
vmax = np.max([BinnerXZ.bin_max(), BinnerYZ.bin_max(), BinnerXY.bin_max()])
levels = np.linspace(vmin,vmax,20)
xs_c = np.arange(*BinnerXZ.limits[0])
zs_c = np.arange(*BinnerXZ.limits[1])
ZS_C, XS_C = np.meshgrid(zs_c,xs_c)
ax[0].contourf(X=XS_C, Y=BinnerXZ.bins, Z=ZS_C,
zdir='y', offset=Y_max,
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
xs_c = np.arange(*BinnerXY.limits[0])
ys_c = np.arange(*BinnerXY.limits[1])
YS_C, XS_C = np.meshgrid(ys_c,xs_c)
ax[0].contourf(X=XS_C, Y=YS_C, Z=BinnerXY.bins,
zdir='z', offset=Z_min,
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
ys_c = np.arange(*BinnerYZ.limits[0])
zs_c = np.arange(*BinnerYZ.limits[1])
ZS_C, YS_C = np.meshgrid(zs_c, ys_c)
ax[0].contourf(X=BinnerYZ.bins, Y=YS_C, Z=ZS_C,
zdir='x', offset=X_min,
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
# Plot scatter of all data
ax[0].scatter(X, Y, Z, c='g', marker='.', alpha=0.2)
ax[0].set_xlabel(r"$x$")
ax[0].set_ylabel(r"$y$")
ax[0].set_zlabel(r"$z$")
max_range = max([X_max-X_min, Y_max-Y_min, Z_max-Z_min]) / 2.
pos = [(X_max+X_min)/2., (Y_max+Y_min)/2., (Z_max+Z_min)/2.]
ax[0].set_xlim(pos[0] - max_range, pos[0] + max_range)
ax[0].set_ylim(pos[1] - max_range, pos[1] + max_range)
ax[0].set_zlim(pos[2] - max_range, pos[2] + max_range)
# Plot 2D histograms
BinnerXZ.plot_2d_slice(fig=fig, ax=ax[1], xlabel=r"$x$", ylabel=r'$z$')
BinnerXY.plot_2d_slice(fig=fig, ax=ax[2], xlabel=r"$x$", ylabel=r'$y$')
BinnerYZ.plot_2d_slice(fig=fig, ax=ax[3], xlabel=r"$y$", ylabel=r'$z$')
plt.show()
You can also use only one Binner, but notice that you will get artifacts where the planes intersect:
# ...
# Using three Binners
# ...
# Using only one Binner (adds a small error! see comments!)
Binner = ba.Binning_Array([[X_min, X_max, step],
[Y_min, Y_max, step],
[Z_min, Z_max, step]])
for point in data:
Binner.add_value([point[0], point[1], Z_min])
Binner.add_value([point[0], Y_max-step, point[2]])
Binner.add_value([X_min, point[1], point[2]])
fig = plt.figure()
ax = [fig.add_subplot(221, projection='3d'),
fig.add_subplot(222),
fig.add_subplot(223),
fig.add_subplot(224)]
ax[0].scatter(X, Y, Z, c='g', marker='.', alpha=0.2)
Binner.plot_slices(others={0:X_min, 1:Y_max, 2:Z_min}, fig=fig, ax=ax)
plt.show()
The binning_array.py was made for a school project and is not entirely polished, but it's enough for what you want.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
class Binning_Array:
def __init__(self, limits=[[-1.,1.,1.],[-1.,1.,1.]]):
"""Create a new binning array.
The amount of given limits determines the dimension of the array,
although only 2 and 3D have been tested.
Each limit must be a list of start, stop and step for the
axis it represents (x, y or z)."""
self.limits = np.array(limits)
self._shape = []
for i in xrange(len(self.limits)):
self._shape.append((self.limits[i][1]-self.limits[i][0]) / \
float(self.limits[i][2]))
self._shape = tuple(self._shape)
self.dimensions = len(self._shape)
self.bins = np.zeros(self._shape)
self.outside = 0.
self._normalized = 1.
def __repr__(self):
"""Representation method. <<REVIEW>>"""
return "Binning Array! Hurray!"
def __getitem__(self, index):
"""Direct acess to read self.bins (use something like self[index])
Direct acess to write would be given by __setitem__
"""
return self.bins.__getitem__(index)
def position2index(self,position,axis=0):
"""Convert a given position to an index in axis.
If it is outside, it returns -1.
"""
if self.limits[axis][0] <= position < self.limits[axis][1]:
return int((position - self.limits[axis][0]) / self.limits[axis][2])
else: return -1
def index2position(self, index, axis=0):
"""Convert a given index to a position in axis.
If it is outisde, it returns -1.
"""
if 0 <= index < self._shape[axis]:
return self.limits[axis][0] + self.limits[axis][2] * index
else:
return -1
def add_value(self, position, value=1., verbose=False):
"""Add a given value to a specified position.
If verbose it returns a list with the axies at which the position
is outside the scope of this Binning_Array.
Not very efficient because of that (verbose was for debugging).
"""
indexs = []
outside = False
if verbose:
outs = []
for i in xrange(self.dimensions):
# using self.dimensions serves as a filter
# if position has non valid shape
index = self.position2index(position[i],i)
if index == -1:
if verbose:
outside = True
outs.append(i)
else:
self.outside += value / self._normalized
return None # nothing, as it is not verbose
else:
indexs.append(index)
if outside: # the only way to get here is if verbose is True...
self.outside += value / self._normalized
return outs # so I can just return outs...
else:
self.bins[tuple(indexs)] += value / self._normalized
if verbose:
return outs
def get_value(self, position, verbose=False):
"""Return the value at the specified position.
If verbose it alse returns a list with the axies at which the position
is outside the scope of this Binning_Array.
"""
indexs = []
outside = False
if verbose:
outs = []
for i in xrange(self.dimensions):
index = self.position2index(position[i],i)
if index == -1:
if verbose:
outside = True
outs.append[i]
else:
return self.outside
else:
indexs.append(index)
if outside: # the only way to get here is if verbose is True
return self.outside, outs # so I can just return outs...
else:
if verbose:
return self.bins[tuple(indexs)], outs
else:
return self.bins[tuple(indexs)]
def normalize(self, total=None):
"""Divide the entire array by the sum of its values (and outside).
Any value added after this will be normalized by the same factor.
"""
if total is None:
total = self.n_counts()
self.bins /= total
self.outside /= total
self.normalize *= total
def n_counts(self):
"""Return the number of counts."""
return np.sum(self.bins) + self.outside
def bin_max(self):
"""Return the value of the largest bin."""
return np.max(self.bins)
def bin_min(self):
"""Return the value of the largest bin."""
return np.min(self.bins)
def plot_2d_slice(self, cuts=[0,1], others={},
fig=None, ax=None, show=True, **kwargs):
"""Plot a 2D slice."""
x = min(cuts)
y = max(cuts)
xs = np.arange(self.limits[x][0],
self.limits[x][1] + self.limits[x][2],
self.limits[x][2])
ys = np.arange(self.limits[y][0],
self.limits[y][1] + self.limits[y][2],
self.limits[y][2])
index = []
title = ''
for i in xrange(self.dimensions):
if i in cuts:
appendix = slice(self._shape[i]+1)
else:
appendix = others.get(i,(self.limits[i][0]+
self.limits[i][1]) / 2.)
title += '%d:%.4e\t' % (i,appendix)
appendix = self.position2index(appendix,i)
index.append(appendix)
index = tuple(index)
if fig is None:
fig, ax = plt.subplots(1,1)
YS,XS = np.meshgrid(ys, xs)
graph = ax.pcolormesh (XS, YS, self.bins[index], cmap=plt.cm.coolwarm)
fig.colorbar(graph, ax=ax)
ax.axis('equal')
ax.set_xlim(self.limits[x][0], self.limits[x][1])
ax.set_ylim(self.limits[y][0], self.limits[y][1])
if 'xticks' in kwargs:
ax.set_xticks(kwargs['xticks'])
if 'yticks' in kwargs.keys():
ax.set_yticks(kwargs['yticks'])
if 'xlabel' in kwargs:
ax.set_xlabel(kwargs['xlabel'])
if 'ylabel' in kwargs:
ax.set_ylabel(kwargs['ylabel'])
if 'xlim' in kwargs:
ax.set_xlim(*kwargs['xlim'])
if 'ylim' in kwargs:
ax.set_ylim(*kwargs['ylim'])
if show:
fig.tight_layout()
fig.show()
def plot_slices(self, others={}, fig=None, ax=None,
show=True, projections=True):
index = []
pos = []
title = ''
for i in xrange(self.dimensions):
temp = others.get(i,(self.limits[i][0]+self.limits[i][1])/2.)
title += '%d:%.4e\t' % (i,temp)
pos.append(temp)
index.append(self.position2index(temp,i))
if self.dimensions == 3:
if fig is None:
fig = plt.figure()
if projections:
ax = [fig.add_subplot(221, projection='3d'),
fig.add_subplot(222),
fig.add_subplot(223),
fig.add_subplot(224)]
else:
ax = fig.add_subplot(111, projection='3d')
if projections:
xs = np.arange(self.limits[0][0],
self.limits[0][1] + self.limits[0][2],
self.limits[0][2])
ys = np.arange(self.limits[1][0],
self.limits[1][1] + self.limits[1][2],
self.limits[1][2])
zs = np.arange(self.limits[2][0],
self.limits[2][1] + self.limits[2][2],
self.limits[2][2])
xs_c = np.arange(*self.limits[0])
ys_c = np.arange(*self.limits[1])
zs_c = np.arange(*self.limits[2])
vmin = np.min(self.bins)
vmax = np.max(self.bins)
levels = np.linspace(vmin,vmax,20)
#graph 0 (3D)
ax[0].set_xlabel(r"$x$")
ax[0].set_ylabel(r"$y$")
ax[0].set_zlabel(r"$z$")
#ax[0].axis('equal') #not supported in 3D:
#http://stackoverflow.com/questions/13685386/\
#matplotlib-equal-unit-length-with-equal-aspect-ratio-z-axis-is-not-equal-to
max_range = max([xs[-1]-xs[0],ys[-1]-ys[0],zs[-1]-zs[0]]) / 2.
# x_mean = (xs[-1] + xs[0])/2.
# y_mean = (ys[-1] + ys[0])/2.
# z_mean = (zs[-1] +zs[0])/2.
ax[0].set_xlim(pos[0] - max_range, pos[0] + max_range)
ax[0].set_ylim(pos[1] - max_range, pos[1] + max_range)
ax[0].set_zlim(pos[2] - max_range, pos[2] + max_range)
# to understand holes in contour plot:
#http://stackoverflow.com/questions/18897950/\
#matplotlib-pyplot-contourf-function-introduces-holes-or-gaps-when-plotting-regul
# graph 1 (2D)
ZS, XS = np.meshgrid(zs,xs)
ZS_C, XS_C = np.meshgrid(zs_c,xs_c)
ax[1].pcolormesh(XS, ZS, self.bins[:,index[1],:],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm)
ax[0].contourf(X=XS_C, Y=self.bins[:,index[1],:], Z=ZS_C,
zdir='y', offset=pos[1],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
ax[1].set_xlabel(r"$x$")
ax[1].set_ylabel(r"$z$")
ax[1].set_xlim(xs[0],xs[-1])
ax[1].set_ylim(zs[0],zs[-1])
ax[1].axis('equal')
# graph 2 (2D)
YS, XS = np.meshgrid(ys,xs)
YS_C, XS_C = np.meshgrid(ys_c,xs_c)
ax[2].pcolormesh(XS, YS, self.bins[:,:,index[2]],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm)
ax[0].contourf(X=XS_C, Y=YS_C, Z=self.bins[:,:,index[2]],
zdir='z', offset=pos[2],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
ax[2].set_xlabel(r"$x$")
ax[2].set_ylabel(r"$y$")
ax[2].set_xlim(xs[0],xs[-1])
ax[2].set_ylim(ys[0],ys[-1])
ax[2].axis('equal')
# graph 3 (2D)
ZS, YS = np.meshgrid(zs, ys)
ZS_C, YS_C = np.meshgrid(zs_c, ys_c)
ax[3].pcolormesh(YS, ZS, self.bins[index[0],:,:],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm)
ax[0].contourf(X=self.bins[index[0],:,:], Y=YS_C, Z=ZS_C,
zdir='x', offset=pos[0],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
ax[3].set_xlabel(r"$y$")
ax[3].set_ylabel(r"$z$")
ax[3].set_xlim(ys[0],ys[-1])
ax[3].set_ylim(zs[0],zs[-1])
ax[3].axis('equal')
else:
# update to draw a given slice, use it to plot eaxh axes above!
ax.plot(self.XS,self.YS,self.ZS)
ax.set_zlabel(r"$z$")
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$y$")
ax.axis('equal')
else:
if fig is None:
fig, ax = plt.subplots(1)
xs = np.arange(self.limits[0][0],
self.limits[0][1] + self.limits[0][2],
self.limits[0][2])
ys = np.arange(self.limits[1][0],
self.limits[1][1] + self.limits[1][2],
self.limits[1][2],)
YS, XS = np.meshgrid(ys, xs)
graph = ax.pcolormesh(XS, YS, self.bins, cmap=plt.cm.coolwarm)
fig.colorbar(graph)
ax.set_xlim(self.limits[0][0], self.limits[0][1])
ax.set_ylim(self.limits[1][0], self.limits[1][1])
ax.set_title('Energy Distribution')
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$y$")
ax.axis('equal')
if show:
fig.tight_layout()
fig.show()
return fig, ax
If anything on the code is wrong or bugged please say so and I will edit the above (the school project was already graded, so you won't be doing my homework).
Also, if anything is less than clear please say so I add comments or explanations as needed.
Related
I have the following code that should draw a cycloid with animation and save it to a gif
but after running the program, a white square appears that covers everything, I can't find the reason cycloid_animation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation, PillowWriter
plt.rcParams['animation.html'] = 'html5'
R = 1
def circle(a, b, r):
# (a,b): the center of the circle
# r: the radius of the circle
# T: The number of the segments
T = 100
x, y = [0]*T, [0]*T
for i,theta in enumerate(np.linspace(0,2*np.pi,T)):
x[i] = a + r*np.cos(theta)
y[i] = b + r*np.sin(theta)
return x, y
# Calculate the cycloid line
thetas = np.linspace(0,4*np.pi,100)
cycloid_x = R*(thetas-np.sin(thetas))
cycloid_y = R*(1-np.cos(thetas))
cycloid_c = R*thetas
fig = plt.figure()
lns = []
trans = plt.axes().transAxes
for i in range(len(thetas)):
x,y = circle(cycloid_c[i], R, R)
ln1, = plt.plot(x, y, 'g-', lw=2)
ln2, = plt.plot(cycloid_x[:i+1] ,cycloid_y[:i+1], 'r-', lw=2)
ln3, = plt.plot(cycloid_x[i], cycloid_y[i], 'bo', markersize=4)
ln4, = plt.plot([cycloid_c[i], cycloid_x[i]], [R,cycloid_y[i]], 'y-', lw=2)
tx1 = plt.text(0.05, 0.8, r'$\theta$ = %.2f $\pi$' % (thetas[i]/np.pi), transform=trans)
lns.append([ln1,ln2,ln3,ln4,tx1])
plt.xlim(0,15)
plt.ylim(0,3)
plt.xlabel('x')
plt.ylabel('y')
plt.grid()
plt.axes().set_aspect('equal')
ani = animation.ArtistAnimation(fig, lns, interval=50)
#ani.save('cycloid_ArtistAnimation.mp4',writer='ffmpeg')
ani.save('cycloid_ArtistAnimation.gif',writer='pillow')
ani
Each time you call plt.axis() you are creating a new axis on top of the figure. Since what you want is to get the current axis and then apply the transformations, after creating the figure you should call plt.gca() to get the current axis and use that instead.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation, PillowWriter
plt.rcParams['animation.html'] = 'html5'
R = 1
def circle(a, b, r):
# (a,b): the center of the circle
# r: the radius of the circle
# T: The number of the segments
T = 100
x, y = [0]*T, [0]*T
for i,theta in enumerate(np.linspace(0,2*np.pi,T)):
x[i] = a + r*np.cos(theta)
y[i] = b + r*np.sin(theta)
return x, y
# Calculate the cycloid line
thetas = np.linspace(0,4*np.pi,100)
cycloid_x = R*(thetas-np.sin(thetas))
cycloid_y = R*(1-np.cos(thetas))
cycloid_c = R*thetas
fig = plt.figure()
lns = []
trans = plt.gca().transAxes #<=== HERE
for i in range(len(thetas)):
x,y = circle(cycloid_c[i], R, R)
ln1, = plt.plot(x, y, 'g-', lw=2)
ln2, = plt.plot(cycloid_x[:i+1] ,cycloid_y[:i+1], 'r-', lw=2)
ln3, = plt.plot(cycloid_x[i], cycloid_y[i], 'bo', markersize=4)
ln4, = plt.plot([cycloid_c[i], cycloid_x[i]], [R,cycloid_y[i]], 'y-', lw=2)
tx1 = plt.text(0.05, 0.8, r'$\theta$ = %.2f $\pi$' % (thetas[i]/np.pi), transform=trans)
lns.append([ln1,ln2,ln3,ln4,tx1])
plt.xlim(0,15)
plt.ylim(0,3)
plt.xlabel('x')
plt.ylabel('y')
plt.grid()
plt.gca().set_aspect('equal') #<=== And HERE
ani = animation.ArtistAnimation(fig, lns, interval=50)
#ani.save('cycloid_ArtistAnimation.mp4',writer='ffmpeg')
ani.save('cycloid_ArtistAnimation.gif',writer='pillow')
I want to draw a spin systems. I have the picture below. However, I want the quivers to be INSIDE the scatter circles and the "Main arrow" to be a bright one. Thus, how can I order three plots?
Thank You in advance!
Here is the code:
import matplotlib.pyplot as plt
from matplotlib import cm, colors
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
############################## A sphere #################################
r = 4
pi = np.pi
cos = np.cos
sin = np.sin
phi, theta = np.mgrid[0.0:pi:100j, 0.0:2.0*pi:100j]
x = r*sin(phi)*cos(theta)
y = r*sin(phi)*sin(theta)
z = r*cos(phi)
#Set colours and render
fig = plt.figure(figsize=(10,10))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, rstride=1, cstride=1, color='red', alpha=0.6, linewidth=0)
############################ One arrow ########################
x1 = 0
y1 = 0
z1 = 0
dx1 = 0
dy1 = 0
dz1 = 2
ax.quiver(x1, y1, z1, dx1, dy1, dz1, pivot='middle', color="blue", linewidths=5.0,length=1.4)
ax.scatter(0, 0, 0,color="blue", s=800)
############################ Array of arrows ############################################
ax.set_xlim([-4,4])
ax.set_ylim([-4,4])
ax.set_zlim([-4,4])
ax.set_aspect("equal")
plt.tight_layout()
N = 5 # number of points - 5x5x5
scale = 0.4 # scale for the increment of an arrow
ax = fig.gca(projection='3d')
xx = np.linspace(-3.5, 3.5, N)
yy = np.linspace(-3.5, 3.5, N)
zz = np.linspace(-3.5, 3.5, N)
dxx = scale*np.random.rand(N)
dyy = scale*np.random.rand(N)
dzz = scale*np.random.rand(N)
x, y, z = np.meshgrid(yy, zz, xx)
dx, dy, dz = np.meshgrid(dxx, dyy, dzz)
ax.scatter(x, y, z,color="orange", s=200)
ax.quiver(x, y, z, dx, dy, dz, pivot='middle', length=1.6, color="blue")
plt.show()
I've made a script which uses matplotlib's FuncAnimation function to animate a series of contour plots for paraboloid surface functions. I'd like to add a colorbar for which the range does not change throughout the entire animation. I really have no idea how to do this. The script is shown below:
import numpy as np
import itertools
import matplotlib.pyplot as plt
import matplotlib.mlab as ml
import matplotlib.animation as animation
#Generate some lists
def f(x,y,a):
return a*(x**2+y**2)
avals = list(np.linspace(0,1,10))
xaxis = list(np.linspace(-2,2,9))
yaxis = list(np.linspace(-2,2,9))
xy = list(itertools.product(xaxis,yaxis))
xy = list(map(list,xy))
xy = np.array(xy)
x = xy[:,0]
y = xy[:,1]
x = list(x)
y = list(y)
zlist = []
for a in avals:
z = []
for i, xval in enumerate(x):
z.append(f(x[i],y[i],a))
zlist.append(z)
xi = np.linspace(min(x),max(x),len(x))
yi = np.linspace(min(y), max(y), len(y))
fig,ax = plt.subplots()
def animate(index):
zi = ml.griddata(x, y, zlist[index], xi, yi, interp='linear')
ax.clear()
contourplot = ax.contourf(xi, yi, zi, cmap=plt.cm.hsv,origin='lower')
#cbar = plt.colorbar(contourplot)
ax.set_title('%03d'%(index))
return ax
ani = animation.FuncAnimation(fig,animate,np.array([0,1,2,3,4,5,6,7,8,9]),interval=200,blit=False)
plt.show()
Line 42 was my attempt at including said colorbar. The issue here is that because FuncAnimation calls the plotting function multiple times (once for each frame), the colorbar gets plotted multiple times thus messing up the animation. I also can't think of any way to move the colorbar instantiation outside of the animate function since the ax object appears to be local to it.
How can I put one colorbar for the whole animation?
Please note the above is fully working code. It should work on the appropriate python interpreter.
I guess the idea would be to create a contour plot outside the updating function once and give it a colorbar. The contour plot would then need to have defined levels and the colorrange needs to be defined.
ax.contourf(..., levels=levels, vmin=zmin, vmax=zmax)
where zmin and zmax are the minimum and maximum data to be shown, and levels is the list or array of levels to use.
Then, inside the animating function, you would only create a new contour plot with those same parameters without touching the colorbar at all.
import numpy as np
import itertools
import matplotlib.pyplot as plt
import matplotlib.mlab as ml
import matplotlib.animation as animation
def f(x,y,a):
return a*(x**2+y**2)
avals = list(np.linspace(0,1,10))
xaxis = list(np.linspace(-2,2,9))
yaxis = list(np.linspace(-2,2,9))
xy = list(itertools.product(xaxis,yaxis))
xy = np.array(list(map(list,xy)))
x = xy[:,0]
y = xy[:,1]
zlist = []
for a in avals:
z = []
for i, xval in enumerate(x):
z.append(f(x[i],y[i],a))
zlist.append(z)
xi = np.linspace(min(x),max(x),len(x))
yi = np.linspace(min(y), max(y), len(y))
zmin = min([min(zl) for zl in zlist])
zmax = max([max(zl) for zl in zlist])
levels = np.linspace(zmin, zmax,41)
kw = dict(levels=levels, cmap=plt.cm.hsv, vmin=zmin, vmax=zmax, origin='lower')
fig,ax = plt.subplots()
zi = ml.griddata(x, y, zlist[0], xi, yi, interp='linear')
contourplot = ax.contourf(xi, yi, zi, **kw)
cbar = plt.colorbar(contourplot)
def animate(index):
zi = ml.griddata(x, y, zlist[index], xi, yi, interp='linear')
ax.clear()
ax.contourf(xi, yi, zi, **kw)
ax.set_title('%03d'%(index))
ani = animation.FuncAnimation(fig,animate,10,interval=200,blit=False)
plt.show()
As usual, I got beaten to the punch by #ImportanceOfBeingErnest, but I have a slightly different approach, which I thinks works as well.
I created a separate axe for the color bar, and I created a standalone color bar using the example from matplotlib's documentation. This requires to know the extend of the color scale before hand though.
Then I just plot the contourf in the animation using the same colorbar and normalization.
#Generate some lists
def f(x,y,a):
return a*(x**2+y**2)
avals = list(np.linspace(0,1,10))
xaxis = list(np.linspace(-2,2,9))
yaxis = list(np.linspace(-2,2,9))
xy = list(itertools.product(xaxis,yaxis))
xy = list(map(list,xy))
xy = np.array(xy)
x = xy[:,0]
y = xy[:,1]
x = list(x)
y = list(y)
zlist = []
for a in avals:
z = []
for i, xval in enumerate(x):
z.append(f(x[i],y[i],a))
zlist.append(z)
xi = np.linspace(min(x),max(x),len(x))
yi = np.linspace(min(y), max(y), len(y))
fig,[ax,cax] = plt.subplots(1,2, gridspec_kw={"width_ratios":[10,1]})
# Set the colormap and norm to correspond to the data for which
# the colorbar will be used.
cmap = mpl.cm.hsv
norm = mpl.colors.Normalize(vmin=0, vmax=10)
cb1 = mpl.colorbar.ColorbarBase(cax, cmap=cmap,
norm=norm,
orientation='vertical')
def animate(index):
zi = ml.griddata(x, y, zlist[index], xi, yi, interp='linear')
ax.clear()
contourplot = ax.contourf(xi, yi, zi, cmap=cmap, norm=norm, origin='lower')
#cbar = plt.colorbar(contourplot)
ax.set_title('%03d'%(index))
return ax
ani = animation.FuncAnimation(fig,animate,np.array([0,1,2,3,4,5,6,7,8,9]),interval=200,blit=False)
Here is a lazy way to add colorbar. Instead of updating colorbar object, this code delete and create all objects in fig.
N = 10 # number of color steps
vmin, vmax = 0, 10 # this should be min and max of z
V = np.linspace(vmin, vmax, N)
fig = plt.figure()
def animate(index):
fig.clear()
ax = plt.subplot(1,1,1)
zi = ml.griddata(x, y, zlist[index], xi, yi, interp='linear')
contourplot = ax.contourf(xi, yi, zi, V, cmap=plt.cm.hsv,origin='lower')
cbar = plt.colorbar(contourplot)
ax.set_title('%03d'%(index))
return ax
I have a polygon radial graph, however I cannot figure out how to make the y-tick lines polygon like the rest of the graph.
radar.py:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.path import Path
from matplotlib.spines import Spine
from matplotlib.projections.polar import PolarAxes
from matplotlib.projections import register_projection
def _radar_factory(num_vars):
theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars)
theta += np.pi/2
def unit_poly_verts(theta):
x0, y0, r = [0.5] * 3
verts = [(r*np.cos(t) + x0, r*np.sin(t) + y0) for t in theta]
return verts
class RadarAxes(PolarAxes):
name = 'radar'
RESOLUTION = 1
def fill(self, *args, **kwargs):
closed = kwargs.pop('closed', True)
return super(RadarAxes, self).fill(closed=closed, *args, **kwargs)
def plot(self, *args, **kwargs):
lines = super(RadarAxes, self).plot(*args, **kwargs)
for line in lines:
self._close_line(line)
def _close_line(self, line):
x, y = line.get_data()
# FIXME: markers at x[0], y[0] get doubled-up
if x[0] != x[-1]:
x = np.concatenate((x, [x[0]]))
y = np.concatenate((y, [y[0]]))
line.set_data(x, y)
def set_varlabels(self, labels):
self.set_thetagrids(theta * 180/np.pi, labels)
def _gen_axes_patch(self):
verts = unit_poly_verts(theta)
return plt.Polygon(verts, closed=True, edgecolor='k')
def _gen_axes_spines(self):
spine_type = 'circle'
verts = unit_poly_verts(theta)
verts.append(verts[0])
path = Path(verts)
spine = Spine(self, spine_type, path)
spine.set_transform(self.transAxes)
return {'polar': spine}
register_projection(RadarAxes)
return theta
def radar_graph(labels = [], values = []):
N = len(labels)
theta = _radar_factory(N)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='radar')
ax.set_ylim(0,100)
ax.set_yticks([20, 40, 60, 80, 100])
ax.set_yticklabels(['','','','',''])
ax.plot(theta, values, color='k', linestyle='dashed', linewidth=.2)
ax.fill(theta, values, color='#23B5BA')
ax.set_varlabels(labels)
plt.savefig("radar.png", dpi=100)
Here is how I call to radar.py to generate the graph:
from radar import radar_graph
labels = ['1', '2', '3', '4', '5']
values = [65, 66, 53, 54, 78]
radar_graph(labels, values)
As you can see in this image the y-axis are circles but the overall structure is a polygon.Radar Image:
Maybe there is a way to transform the yticks similar to the spines.
Solution thought of by ImportanceOfBeingErnest, instead of trying to change the default circle sub-grid we just hide it and plot the sub-grid we want as polygon's.
We can add the following to Radar.py
#Hide Circle Grid
ax.grid(alpha=0.0)
#Draw Sub-axis Grid
ax.plot(theta, [.5,.5,.5,.5,.5], color='grey', linestyle='solid', linewidth=.2, alpha=1)
ax.plot(theta, [20,20,20,20,20], color='grey', linestyle='solid', linewidth=.2, alpha=1)
ax.plot(theta, [40,40,40,40,40], color='grey', linestyle='solid', linewidth=.2, alpha=1)
ax.plot(theta, [60,60,60,60,60], color='grey', linestyle='solid', linewidth=.2, alpha=1)
ax.plot(theta, [80,80,80,80,80], color='grey', linestyle='solid', linewidth=.2, alpha=1)
While I am sure there is a more elegant solution, this works.
I'm simply trying to plot a surface and its contour in 3D, exactly as in this example.
This is the code I'm using to do it:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm
import numpy
def plot_3d_contour(x_dim, y_dim, x_steps, y_steps, scalar_field, file_path):
fig = plt.figure()
x, y = numpy.mgrid[-x_dim/2:x_dim/2:x_steps*1j, -y_dim/2:y_dim/2:y_steps*1j]
v_min = numpy.min(scalar_field)
v_max = nupmy.max(scalar_field)
ax = fig.gca(projection='3d')
cset = ax.contourf(x, y, scalar_field, zdir='z', offset=v_min, cmap=cm.coolwarm)
cset = ax.contourf(x, y, scalar_field, zdir='x', offset=-x_dim/2-1, cmap=cm.coolwarm)
cset = ax.contourf(x, y, scalar_field, zdir='y', offset=y_dim/2+1, cmap=cm.coolwarm)
ax.plot_surface(x, y, scalar_field, rstride=10, cstride=10, alpha=0.3)
ax.set_xlabel('X')
ax.set_xlim(-x_dim/2-1, x_dim/2+1)
ax.set_ylabel('Y')
ax.set_ylim(-y_dim/2-1, y_dim/2+1)
ax.set_zlabel('Z')
ax.set_zlim(v_min, v_max)
plt.savefig(file_path + '.jpg')
plt.close()
scalar_field = numpy.loadtxt('../scalar_field', delimiter=",")
plot_3d_contour(12, 12, 100, 100, scalar_field, 'scalar_field3D')
However, I'm getting a weird behavior in which the a contour (zdir=y) is being over the surface. Besides, I'm getting a weird contour in z_dir=z (with a section missing):
I'm wondering what I'm missing. The scalar field can be found here.
I agree with Ajean. I believe the problem arises because each matplotlib's artist (i.e. PolygonCollection) is rendered separately. There is no way different faces from the same object to be rendered on different sides of another object in the scene.
Here is a useful piece of code :
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
file_path = "./3D_surface_and_contour.jpg"
p = 0.05
f = -0.01
def get_data(p):
x, y, z = axes3d.get_test_data(p)
z = f * z
return x, y, z
def plot_3d_contour(p, f):
nrows = 4
ncols = 5
x, y, z = get_data(p)
x_min, x_max = np.min(x), np.max(x)
y_min, y_max = np.min(y), np.max(y)
z_min, z_max = np.min(z), np.max(z)
fig = plt.figure(figsize=(15, 10))
for n in range(nrows * ncols):
i = n % ncols
j = n / ncols
k = n + 1
if j == 0:
azim = -60 + (i - 2) * 15
elev = 30
elif j == 1:
azim = -60
elev = 30 + (i - 2) * 5
elif j == 2:
azim = 60 + (i - 2) * 10
elev = 30
elif j == 3:
azim = 60
elev = 30 + (i - 2) * 5
ax = fig.add_subplot(nrows, ncols, k, projection='3d')
ax.set_title("azim=" + str(azim) + " elev=" + str(elev))
ax.tick_params(labelsize=8)
ax.view_init(azim=azim, elev=elev)
ax.plot_surface(x, y, z, rstride=10, cstride=10, alpha=0.3)
ax.contourf(x, y, z, zdir='z', offset=z_min, cmap=cm.coolwarm)
ax.contourf(x, y, z, zdir='x', offset=x_min, cmap=cm.coolwarm)
if j == 0 or j == 1:
ax.contourf(x, y, z, zdir='y', offset=y_max, cmap=cm.coolwarm)
elif j == 2 or j == 3:
ax.contourf(x, y, z, zdir='y', offset=y_min, cmap=cm.coolwarm)
ax.set_xlabel('X')
ax.set_xlim(x_min, x_max)
ax.set_ylabel('Y')
ax.set_ylim(y_min, y_max)
ax.set_zlabel('Z')
ax.set_zlim(z_min, z_max)
plt.savefig(file_path, dpi=80)
plt.close()
plot_3d_contour(p, f)
which gives the following image :
The first two rows are produced by a code similar to yours. You might notice that setting the elevation with view_init to a higher value solve the problem. But it is not satisfactory. I have also determined the influence of the range of the z-values (not shown here), the bug seems to appear only when this range is small (you can use the f parameter to test it) which explain why the example does not suffer from it.
The solution I propose is to replace :
ax.contourf(x, y, scalar_field, zdir='y', offset=y_dim/2+1, cmap=cm.coolwarm)
by :
ax.contourf(x, y, scalar_field, zdir='y', offset=-y_dim/2-1, cmap=cm.coolwarm)
in your code and add this additional line :
ax.view_init(azim=60, elev=30)
As shown in the last two rows of the previous image, this way you will be able to avoid the whims of matplotlib.