issue with func in matplotlib.FuncAnimation not updating data - python

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.set_zlim([0, 90])
x = np.linspace(xL, xR, nx)
z = np.linspace(zL, zR, nz)
X, Z = np.meshgrid(x, z)
r = T[:,5,:,0]
graph = ax.plot_surface(X, Z, r)
def update_graph(q):
r = T[:,5,:,q]
graph.set_3d_properties(r)
return graph
ani = matplotlib.animation.FuncAnimation(fig, update_graph, frames = 11)
plt.show()
I have the code above, T is a 100x100x100x12 matrix, and I want to make an animation showing a surface plot as the 4th axis goes from 0-11. However it seems that the animation portion is not working correctly, and I believe the issue is in my update_graph function that it is not passing back an updated value of r to be used in the plot.

It should be noted that for Poly3DCollection set_3d_properties() is only implemented as the following
def set_3d_properties(self):
# Force the collection to initialize the face and edgecolors
# just in case it is a scalarmappable with a colormap.
self.update_scalarmappable()
self._sort_zpos = None
self.set_zsort('average')
self._facecolors3d = PolyCollection.get_facecolor(self)
self._edgecolors3d = PolyCollection.get_edgecolor(self)
self._alpha3d = PolyCollection.get_alpha(self)
self.stale = True
So it doesn't actually modify the data as it does with other 3d objects (e.g. Line3D).
Instead, I would recommend you do something like this
graph = ax.plot_surface(X, Z, r)
def update_graph(q):
r = T[:,5,:,q]
plt.cla()
ax.set_xlim([0, 1])
ax.set_ylim([0, 1])
ax.set_zlim([0, 90])
graph = ax.plot_surface(X, Z, r)
return graph,
Obviously it is a bit tedious to reset all the axis properties on each update but I don't think there is any easier way.
Also note the trailing comma in return graph, - this is necessary when blit=True because FuncAnimation expects an iterable of artists to be returned. However, the return statement is ignored when blit=False.
Here is the result of a simple example using this approach

Related

How can I use the 'xy' mode in a 3d matplolib quiver plot?

I have been trying to plot a 3d graph a gradient field on matplotlib. However, I realized that the arrows were pointing in the wrong direction and so I looked at the config of quiver. I realized about this flag 'angles' and thought that it could fix my problem. However I get the following error.
AttributeError: 'Line3DCollection' object has no property 'angles'
Here's the code
Fuerza_por_atomo_array = np.zeros(coords.shape[1])
Fuerza_por_atomo_array_vector = np.zeros(coords.shape)
for i, coord_i in enumerate(coords.T):
for j, coord_j in enumerate(coords.T):
if i != j:
Fuerza_por_atomo_array[i] -= dVdr(np.linalg.norm(coord_i-coord_j))
Fuerza_por_atomo_array_vector[:,i] -= \
dVdr(np.linalg.norm(coord_i-coord_j)) * np.array([drdx(coord_i[k]-coord_j[k], np.linalg.norm(coord_i-coord_j)) for k in range(3)]) # Regla de la cadena
X, Y, Z = coords[0,:], coords[1,:], coords[2,:]
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
lat = np.tile(np.linspace(-90, 90, 192), 50)
ax.scatter(coords[0,:], coords[1,:], coords[2,:], c=E_por_atomo, cmap=plt.get_cmap("hot"), marker="o")
plt.quiver(X, Y, Z, Fuerza_por_atomo_array_vector[0,:], Fuerza_por_atomo_array_vector[1,:], Fuerza_por_atomo_array_vector[2,:], length=3, angles="xy")
#ax.set_xlim(-1, x_num); ax.set_zlim(-1, z_num); ax.set_ylim(-1, y_num)
plt.colorbar(mappable=cm.ScalarMappable(\
norm=colors.Normalize(vmin=np.min(E_por_atomo), vmax=np.max(E_por_atomo), clip=False), \
cmap=plt.get_cmap("hot")), \
location='top', ax=ax)
ax.set_xlabel("X") ;ax.set_ylabel("Y"); ax.set_zlabel("Z")
fig.tight_layout()
plt.show()
coords is a (3,N)-array with 3d point coordinates and dVdr is just a numerical funcion, a derivative might say. If this is not the place for such a question, don't be hesitant to expose it.

plotting an mXnXk matrix as a 3d model in python

I have a matrix generated by parsing a file the numpy array is the size 101X101X41 and each entry has a value which represents the magnitude at each point.
Now what I want to do is to plot it in a 3d plot where the 4th dimension will be represented by color. so that I will be able to see the shape of the data points (represent molecular orbitals) and deduce its magnitude at that point.
If I plot each slice of data I get the desired outcome, but in a 2d with the 3rd dimension as the color.
Is there a way to plot this model in python using Matplotlib or equivalent library
Thanks
EDIT:
Im trying to get the question clearer to what I desire.
Ive tried the solution suggested but ive received the following plot:
as one can see, due to the fact the the mesh has lots of zeros in it it "hide" the 3d orbitals. in the following plot one can see a slice of the data, where I get the following plot:
So as you can see I have a certain structure I desire to show in the plot.
my question is, is there a way to plot only the structure and ignore the zeroes such that they won't "hide" the structure.
the code I used to generate the plots:
x = np.linspase(1,101,101)
y = np.linspase(1,101,101)
z = np.linspase(1,101,101)
xx,yy,zz = np.meshgrid(x,y,z)
fig=plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(xx, yy, zz, c=cube.calc_data.flatten())
plt.show()
plt.imshow(cube.calc_data[:,:,11],cmap='jet')
plt.show()
Hope that now the question is much clearer, and that you'd appreciate the question enough now to upvote
Thanks.
you can perform the following:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
epsilon = 2.5e-2 # threshold
height, width, depth = data.shape
global_min = np.inf
global_max = -np.inf
for d in range(depth):
slice = data[:, :, d]
minima = slice.min()
if (minima < global_min): global_min = minima
maxima = slice.max()
if (maxima>global_max): global_max=maxima
norm = colors.Normalize(vmin=minima, vmax=maxima, clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap=cm.jet)
points_gt_epsilon = np.where(slice >= epsilon)
ax.scatter(points_gt_epsilon[0], points_gt_epsilon[1], d,
c=mapper.to_rgba(data[points_gt_epsilon[0],points_gt_epsilon[1],d]), alpha=0.015, cmap=cm.jet)
points_lt_epsilon = np.where(slice <= -epsilon)
ax.scatter(points_lt_epsilon[0], points_lt_epsilon[1], d,
c=mapper.to_rgba(data[points_lt_epsilon[0], points_lt_epsilon[1], d]), alpha=0.015, cmap=cm.jet)
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
ax.set_zlabel('Z Label')
plt.title('Electron Density Prob.')
norm = colors.Normalize(vmin=global_min, vmax=global_max, clip=True)
cax, _ = colorbar.make_axes(ax)
colorbar.ColorbarBase(cax, cmap=cm.jet,norm=norm)
plt.savefig('test.png')
plt.clf()
What this piece of code does is going slice by slice from the data matrix and for each scatter plot only the points desired (depend on epsilon).
in this case you avoid plotting a lot of zeros that 'hide' your model, using your words.
Hope this helps
You can adjust the color and size of the markers for the scatter. So for example you can filter out all markers below a certain threshold by putting their size to 0. You can also make the size of the marker adaptive to the field strength.
As an example:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
f = lambda x,y,z: np.exp(-(x-3)**2-(y-3)**2-(z-1)**2) - \
np.exp(-(x+3)**2-(y+3)**2-(z+1)**2)
t1 = np.linspace(-6,6,101)
t2 = np.linspace(-3,3,41)
# Data of shape 101,101,41
data = f(*np.meshgrid(t1,t1,t2))
print(data.shape)
# Coordinates
x = np.linspace(1,101,101)
y = np.linspace(1,101,101)
z = np.linspace(1,101,41)
xx,yy,zz = np.meshgrid(x,y,z)
fig=plt.figure()
ax = fig.add_subplot(111, projection='3d')
s = np.abs(data/data.max())**2*25
s[np.abs(data) < 0.05] = 0
ax.scatter(xx, yy, zz, s=s, c=data.flatten(), linewidth=0, cmap="jet", alpha=.5)
plt.show()

How to Animate Text in 3D Scatter Plot?

I would like to animate a 3D scatter plot where each data point has a text label that moves along with it.
Right now the text labels do follow the data points as I want, but they persist through each frame; the text does not disappear from the last animation update. See image below. The data points themselves are moving just fine.
2 Questions whose answers might help me >
Is there some way to clear the text without clearing the data points?
My implementation seems a bit clunky. Is there a hidden function similar to _offset3d that works for text objects.
Here's the graphing function:
def graph(data, update_cnt):
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
dots = ax.scatter(data[0][0][:], data[1][0][:], data[2][0][:])
dot_txt = nmp.ones(nmp.size(data,2), dtype=str)
for n in range(0,nmp.size(data, 2)):
dot_txt[n] = ax.text(data[0][0][n], data[1][0][n], data[2][0][n],'%s'%(n))
ani = animation.FuncAnimation(fig, update, update_cnt, fargs=(dots, data, dot_txt, ax), interval=300)
plt.show()
and the animation update function:
def update(num, dots, data, dot_txt, ax):
y = data[0][num][:]
x = data[1][num][:]
z = data[2][num][:]
dots._offsets3d = (x, y, z)
#dot_txt._something_to_update_text_here()
dot_txt = nmp.ones(nmp.size(data,2), dtype=str)
for n in range(0,nmp.size(data, 2)):
dot_txt[n] = ax.text(data[1][num][n], data[0][num][n], data[2][num][n],'%s'%(n))
and the current plot output:
I found a solution.
I think it's important to note that I could not use the more common solutions for 3D animated scatter plots because I need different marker styles for various points. This forces me to iteratively plot each scatter point, rather than passing a list to the update function. However in doing so, the problem of animating the text is solved nicely.
frame_list contains the x,y,z coordinates and styling for each data point in every frame.
def graph(frame_list):
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
frame_cnt = len(frame_list)
ani = animation.FuncAnimation(fig, update_graph, frame_cnt,
fargs=(frame_list, ax, frame_cnt), interval=600)
plt.show()
The biggest contributor the success of this is the ax.clear() call before every frame.
def update_graph(f, frame_list, ax, cnt):
ax.clear()
f = nmp.mod(f, cnt)
frame = frame_list[f]
for n in range(len(frame.marker)):
x, y, z, s, c, m, name = frame.get_params(n)
ax.scatter(x, y, z, s=s, c=c, marker=m)
ax.text(x, y, z, '%s'%(name))
The get_params function returns all of the relevant data for that frame.

Maintaining one colorbar for maptlotlib FuncAnimation

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

Shifting the triangles order to match colormap

This question is a sequel of a previous one but regarding this time the colormap and the order of the triangle. I want to interpolate experimental data over a surface so as to enable a continuous colormap with however the surface known only at its corner node. To interpolate, I put a canonical example which works quite well but fails on real data.
Indeed as shown in the example below, the initial triangulation results in two triangles with a huge gap between them, cf first picture. When the interpolation is done, it doesn't get any better and the colormap is also lost, cf. second picture. The best so far is by interverting z and y to get adjacent triangles from the beginning which results in a successful interpolation. However as you might notice in the third picture, the surface is tilted by 90° which is normal since I switch y for z and vice-versa.
However when I switch back y and z in the tri_surf function with ax.plot_trisurf(new.x, new_z, new.y, **kwargs), the colormap doesn't follow, cf. picture 4.
I thought of rotating the colormap in somehow or generate new triangles from the interpolated ones with triang = tri.Triangulation(new.x, new_z) but without any success. So any idea or hint about properly doing the initial triangulation with two adjacent triangles, as for the third picture, but with the surface oriented correclty and ultimately the colormap proportional to the Y-value.
import numpy
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.tri as tri
x=numpy.array([0.00498316, 0.00498316, 0.00996632, 0.00996632])
y=numpy.array([-0.00037677, -0.00027191, -0.00078681, -0.00088475])
z=numpy.array([0., -0.0049926, 0., -0.00744763])
# Initial Triangle
fig = plt.figure()
ax = Axes3D(fig)
triang = tri.Triangulation(x, y)
norm = plt.Normalize(vmax=y.max(), vmin=y.min())
ax.plot_trisurf(x, y, z, triangles=triang.triangles)
# Interpolated Triangle
fig = plt.figure()
ax = Axes3D(fig)
triang = tri.Triangulation(x, y)
refiner = tri.UniformTriRefiner(triang)
interpolator = tri.LinearTriInterpolator(triang, z)
new, new_z = refiner.refine_field(z, interpolator, subdiv=4)
kwargs = dict(triangles=new.triangles, cmap=cm.jet, norm=norm, linewidth=0, antialiased=False)
ax.plot_trisurf(new.x, new.y, new_z, **kwargs)
# Best so far
fig = plt.figure()
ax = Axes3D(fig)
triang = tri.Triangulation(x, z)
refiner = tri.UniformTriRefiner(triang)
interpolator = tri.LinearTriInterpolator(triang, y)
new, new_z = refiner.refine_field(y, interpolator, subdiv=4)
kwargs = dict(triangles=new.triangles, cmap=cm.jet, norm=norm, linewidth=0, antialiased=False)
ax.plot_trisurf(new.x, new.y, new_z, **kwargs)
plt.show()
Apparently the automatic triangulation doesn't produce the right triangles for you, but you can specify how you want your triangles manually:
triang = tri.Triangulation(x, y, [[3,2,1],[1,2,0]])
# alternatively:
triang = tri.Triangulation(x, y, [[3,2,0],[1,3,0]])
These two ways give rather different results:
However, now the interpolation becomes awkward, because for some (x,y) there are multiple z-values.. One way of bypassing this issue is interpolating and plotting the 2 large triangles separately:
import numpy
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import matplotlib.tri as tri
def plot_refined_tri(x, y, z, ax, subdiv=4, **kwargs):
triang = tri.Triangulation(x, y)
refiner = tri.UniformTriRefiner(triang)
interpolator = tri.LinearTriInterpolator(triang, z)
new, new_z = refiner.refine_field(z, interpolator, subdiv=subdiv)
ax.plot_trisurf(new.x, new.y, new_z, triangles=new.triangles, **kwargs)
x=numpy.array([0.00498316, 0.00498316, 0.00996632, 0.00996632])
y=numpy.array([-0.00037677, -0.00027191, -0.00078681, -0.00088475])
z=numpy.array([0., -0.0049926, 0., -0.00744763])
fig = plt.figure()
ax = Axes3D(fig)
# note: I normalized on z-values to "fix" the colormap
norm = plt.Normalize(vmax=z.max(), vmin=z.min())
kwargs = kwargs = dict(linewidth=0.2, cmap=cm.jet, norm=norm)
idx = [3,2,1]
plot_refined_tri(x[idx], y[idx], z[idx], ax, **kwargs)
idx = [1,2,0]
plot_refined_tri(x[idx], y[idx], z[idx], ax, **kwargs)
plt.show()
Result:

Categories

Resources