How to Animate Text in 3D Scatter Plot? - python

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.

Related

issue with func in matplotlib.FuncAnimation not updating data

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

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()

Plotting surfaces in Python [duplicate]

This question already has answers here:
surface plots in matplotlib
(9 answers)
How to surface plot/3d plot from dataframe
(2 answers)
Closed 4 years ago.
I'm trying to plot some data, which consists of 4 variables. I'm using 2 approaches one is scatter plot and another one is surface. The problem is that when I'm using surface the data is missing. I think it has to do with the color setup.
For the scatter plot, I use this:
def scatter3d(x,y,z, cs, colorsMap='jet'):
cm = plt.get_cmap(colorsMap)
cNorm = matplotlib.colors.Normalize(vmin=min(cs), vmax=max(cs))
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x, y, z,c=scalarMap.to_rgba(cs))
ax.set_xlabel('Thita1')
ax.set_ylabel('Thita2')
ax.set_zlabel('Fairness (%)')
scalarMap.set_array(cs)
fig.colorbar(scalarMap,label='Error Rate (%)')
plt.show()
I want to convert it to a surface plot, using this:
def surfacePlot(x,y,z, cs, colorsMap='jet'):
cm = plt.get_cmap(colorsMap)
cNorm = matplotlib.colors.Normalize(vmin=min(cs), vmax=max(cs))
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, facecolors=scalarMap.to_rgba(cs))
ax.set_xlabel('Thita1')
ax.set_ylabel('Thita2')
ax.set_zlabel('Fairness')
scalarMap.set_array(cs)
fig.colorbar(scalarMap,label='Error Rate (%)')
plt.show()
However, this results in an empty grid:
Although the axes have received the min and max values from the vectors, the points are missing. What am I doing wrong ?
As mentioned, plot_surface requires 2d array data, or mesh--similar to how you would create a heatmap if you are familiar with that. If your data is regularly spaced across x,y axis (which it seems you do), you can simply use the z data formatted into a 2d array as shown in previous examples linked in the comments:
grid_x, grid_y = np.meshgrid(x, y)
# I'm assuming that your data is already mesh-like, which it looks like it is.
# The data would also need to be appropriately sorted for `reshape` to work.
# `dx` here is number of unique x values, and `dy` is number unique y values.
grid_z = z.reshape(dy, dx)
ax.plot_scatter(grid_x, grid_y, grid_z)
However, in the general case where you have unevenly spaced x,y,z points, you can interpolate your data to create your mesh. Scipy has the function griddata that will interpolate onto a defined meshgrid. You can use this to plot your data:
from scipy.interpolate import griddata
xy = np.column_stack([x, y])
grid_x, grid_y = np.mgrid[0:1:100j, 0:1:100j] # grid you create
grid_z = griddata(xy, z, (grid_x, grid_y))
ax.plot_scatter(grid_x, grid_y, grid_z)

Matplotlib animation of the values of a 3D array in python

I am currently want to visualize 3D-rawdata out of my Walabot device and display it in a 3D animation created with matplotlib FuncAnimation. I already searched for answers, but I could not find anything helpful.
In my case I already have a 3 dimensional array, where each index has a specific value, which changes over the time. I already could figure out how to display it in a 3D chart with different colors and sizes but now I want to make update itself. I have found some example code which gave me a good start, but my chart does not update on its own. I have to close the window and then the window pops up again with different values from the 3D array. Do you guys know how to solve this problem?
Here is my code so far:
def update(plot, signal, figure):
plot.clear()
scatterplot = plot.scatter(x, y, z, zdir='z', s=signal[0], c=signal[0])
figure.show()
return figure
def calc_RasterImage(signal):
# 3D index is represnted is the following schema {i,j,k}
# sizeX - signal[1] represents the i dimension length
# sizeY - signal[2] represents the j dimension length
# sizeZ - signal[3] represents the k dimension length
# signal[0][i][j][k] - represents the walabot 3D scanned image (internal data)
#Initialize 3Dplot with matplotlib
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim([xMin-1,xMax-1])
ax.set_ylim([yMin-1,yMax-1])
ax.set_zlim([zMin-1,zMax-1])
ax.set_xlabel('X AXIS')
ax.set_ylabel('Y AXIS')
ax.set_zlabel('Z AXIS')
scatterplot = ax.scatter(x, y, z, zdir='z', s=signal[0], c= signal[0])
cbar = plt.colorbar(scatterplot)
cbar.set_label('Density')
#def update(signal):
# ax.clear()
# scatterplot = ax.scatter(x, y, z, zdir='z', s=signal[0], c=signal[0])
ani = anim.FuncAnimation(fig, update(ax, signal, plt), frames=10 , blit=True, repeat = True)
def main():
wlbt = Walabot()
wlbt.connect()
if not wlbt.isConnected:
print("Not Connected")
else:
print("Connected")
wlbt.start()
calc_index(wlbt.get_RawImage_values())
while True:
#print_RawImage_values(wlbt.get_RawImage_values())
calc_RasterImage(wlbt.get_RawImage_values())
wlbt.stop()
if __name__ == '__main__':
main()
As you can see the row with
ani = anim.FuncAnimation(fig, update(ax, signal, plt), frames=10 , blit=True, repeat = True)
needs the update function from the top. This function clears my plot and recreates a new plot with different values. But I always need to close the plot window first, which I would like to avoid.
This is how the plot looks like:
3D array plot with matplotlib scatter
Do you guys have an idea how to solve this problem?
cheers
Your code isn't really a minimal working example, and you shouldn't be lazy and actually read the docs for FuncAnimation before coming to SO. That being said, something like this should work:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Display walabot output.
"""
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def display(walabot_instance):
# set x, y, z
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
path_collection = ax.scatter(x, y, z, zdir='z')
# do your labelling, layout etc
def update(ignored, walabot_instance):
signal = walabot_instance.get_RawImage_values()
path_collection.set_sizes(signal[0])
path_collection.set_color(signal[1])
return path_collection,
return FuncAnimation(fig, update, fargs=[walabot_instance])
def main():
wlbt = Walabot()
wlbt.connect()
if not wlbt.isConnected:
print("Not Connected")
else:
print("Connected")
wlbt.start()
plt.ion()
animation = display(wlbt)
raw_input("Press any key when done watching Walabot...")
if __name__ == "__main__":
main()
If you have any questions (after having read the docs!), drop a comment.

Only plot part of a 3d figure using matplotlib

I got a problem when I was plotting a 3d figure using matplotlib of python. Using the following python function, I got this figure:
Here X, Y are meshed grids and Z and Z_ are functions of X and Y. C stands for surface color.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
def plot(X, Y, Z, Z_, C):
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(
X, Y, Z, rstride=1, cstride=1,
facecolors=cm.jet(C),
linewidth=0, antialiased=False, shade=False)
surf_ = ax.plot_surface(
X, Y, Z_, rstride=1, cstride=1,
facecolors=cm.jet(C),
linewidth=0, antialiased=False, shade=False)
ax.view_init(elev=7,azim=45)
plt.show()
But now I want to cut this figure horizontally and only the part whose z is between -1 and 2 remain.
What I want, plotted with gnuplot, is this:
I have tried ax.set_zlim3d and ax.set_zlim, but neither of them give me the desired figure. Does anybody know how to do it using python?
Nice conical intersections you have there:)
What you're trying to do should be achieved by setting the Z data you want to ignore to NaN. Using graphene's tight binding band structure as an example:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# generate dummy data (graphene tight binding band structure)
kvec = np.linspace(-np.pi,np.pi,101)
kx,ky = np.meshgrid(kvec,kvec)
E = np.sqrt(1+4*np.cos(3*kx/2)*np.cos(np.sqrt(3)/2*ky) + 4*np.cos(np.sqrt(3)/2*ky)**2)
# plot full dataset
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(kx,ky,E,cmap='viridis',vmin=-E.max(),vmax=E.max(),rstride=1,cstride=1)
ax.plot_surface(kx,ky,-E,cmap='viridis',vmin=-E.max(),vmax=E.max(),rstride=1,cstride=1)
# focus on Dirac cones
Elim = 1 #threshold
E[E>Elim] = np.nan
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
#ax.plot_surface(kx2,ky2,E2,cmap='viridis',vmin=-Elim,vmax=Elim)
#ax.plot_surface(kx2,ky2,-E2,cmap='viridis',vmin=-Elim,vmax=Elim)
ax.plot_surface(kx,ky,E,cmap='viridis',rstride=1,cstride=1,vmin=-Elim,vmax=Elim)
ax.plot_surface(kx,ky,-E,cmap='viridis',rstride=1,cstride=1,vmin=-Elim,vmax=Elim)
plt.show()
The results look like this:
Unfortunately, there are problems with the rendering of the second case: the apparent depth order of the data is messed up in the latter case: cones in the background are rendered in front of the front ones (this is much clearer in an interactive plot). The problem is that there are more holes than actual data, and the data is not connected, which confuses the renderer of plot_surface. Matplotlib has a 2d renderer, so 3d visualization is a bit of a hack. This means that for complex overlapping surfaces you'll more often than not get rendering artifacts (in particular, two simply connected surfaces are either fully behind or fully in front of one another).
We can get around the rendering bug by doing a bit more work: keeping the data in a single surface by not using nans, but instead colouring the the surface to be invisible where it doesn't interest us. Since the surface we're plotting now includes the entire original surface, we have to set the zlim manually in order to focus on our region of interest. For the above example:
from matplotlib.cm import get_cmap
# create a color mapping manually
Elim = 1 #threshold
cmap = get_cmap('viridis')
colors_top = cmap((E + Elim)/2/Elim) # listed colormap that maps E from [-Elim, Elim] to [0.0, 1.0] for color mapping
colors_bott = cmap((-E + Elim)/2/Elim) # same for -E branch
colors_top[E > Elim, -1] = 0 # set outlying faces to be invisible (100% transparent)
colors_bott[-E < -Elim, -1] = 0
# in nature you would instead have something like this:
#zmin,zmax = -1,1 # where to cut the _single_ input surface (x,y,z)
#cmap = get_cmap('viridis')
#colors = cmap((z - zmin)/(zmax - zmin))
#colors[(z < zmin) | (z > zmax), -1] = 0
# then plot_surface(x, y, z, facecolors=colors, ...)
# or for your specific case where you have X, Y, Z and C:
#colors = get_cmap('viridis')(C)
#colors[(z < zmin) | (z > zmax), -1] = 0
# then plot_surface(x, y, z, facecolors=colors, ...)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# pass the mapped colours as the facecolors keyword arg
s1 = ax.plot_surface(kx, ky, E, facecolors=colors_top, rstride=1, cstride=1)
s2 = ax.plot_surface(kx, ky, -E, facecolors=colors_bott, rstride=1, cstride=1)
# but now we need to manually hide the invisible part of the surface:
ax.set_zlim(-Elim, Elim)
plt.show()
Here's the output:
Note that it looks a bit different from the earlier figures because 3 years have passed in between and the current version of matplotlib (3.0.2) has very different (and much prettier) default styles. In particular, edges are now transparent in surface plots. But the main point is that the rendering bug is gone, which is evident if you start rotating the surface around in an interactive plot.

Categories

Resources