I've been trying to annotate individual points in a 3d scatter plot and getting them updated dynamically.
Referred to this: Matplotlib: Annotating a 3D scatter plot
But I'm using FuncAnimation to dynamically update my points instead, the above link does not have a solution that does that lets you know how you can constantly change the position of your text at every interval of funcanimation.
The issue here is that although I could get the text to be drawn out at the very start, subsequent intervals does not update the position of my texts.
Below is the code
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
class Simulator:
def __init__(self):
s_1 = ((0.5, 0.5, 0.0), (0.5,0.5,0.2), (0.5,0.5,1.0), (1.9,0.5,2.0))
s_2 = ((1.9, 0.5, 0.0), (1.9,0.5,0.2), (1.9,0.5,1.0), (1.9,1.9,2.0))
s_3 = ((1.2, 1.2, 0.0), (1.2,1.2,0.2), (1.2,1.2,1.0), (1.2,1.2,2.5))
s_4 = ((0.5, 1.9, 0.0), (0.5,1.9,0.2), (0.5,1.9,1.0), (0.5,0.5,2.0))
s_5 = ((1.9, 1.9, 0.0), (1.9,1.9,0.2), (1.9,1.9,1.0), (0.5,1.9,2.0))
self.data = {
's_1': {'raw': s_1},
's_2': {'raw': s_2},
's_3': {'raw': s_3},
's_4': {'raw': s_4},
's_5': {'raw': s_5}
}
###### Setup ######
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111, projection='3d')
# Setting the axes properties
self.ax.set_xlim3d([0.0, 3.0])
self.ax.set_xlabel('X')
self.ax.set_ylim3d([0.0, 3.0])
self.ax.set_ylabel('Y')
self.ax.set_zlim3d([0.0, 3.0])
self.ax.set_zlabel('Z')
for point,dic in self.data.items():
dic['x'] = []
dic['y'] = []
dic['z'] = []
dic['length'] = len(dic['raw'])
for coords in dic['raw']:
dic['x'].append(coords[0])
dic['y'].append(coords[1])
dic['z'].append(coords[2])
# Interval in milliseconds
self.anim = animation.FuncAnimation(self.fig, self.update, init_func=self.setup, interval=1000)
plt.show()
def setup(self):
plots = []
for point,dic in self.data.items():
dic['plot'] = self.ax.scatter3D([], [], [], c='red', picker = True)
dic['label'] = self.ax.text3D(dic['x'][0], dic['y'][0], dic['z'][0], point, zorder=1, color='k')
def update(self, i):
plots = []
seq_x = []
seq_y = []
seq_z = []
for point,dic in self.data.items():
if i < dic['length']:
seq_x = dic['x'][i]
seq_y = dic['y'][i]
seq_z = dic['z'][i]
dic['plot']._offsets3d = [seq_x], [seq_y], [seq_z]
#### THIS IS NOT WORKING!!!! ####
dic['label'].set_position((seq_x, seq_y, seq_z))
#### BUT SOMEHOW THIS IS WORKING AND THE TEXT's COLORS GETS UPDATED??? ####
dic['label'].set_color('red')
#### IF SOMEONE IS KIND ENOUGH, I HAVE NO IDEA WHY THIS DOES NOT WORK TOO :( ####
dic['plot'].set_color('blue')
plots.append(dic['plot'])
else:
self.anim.event_source.stop()
print('Simulation ended.')
return plots
Simulator()
As you can see from the image above, the texts are not right beside the points where they should be. What i would like is for the text to follow the points as it rises.
The portion below is NOT part of the main question, but another question that I have:
Does any one know how to change the color of a scatter plot? I've tried set_color('another color') but it is not working, the color of the points does not update itself.
@ImportanceOfBeingErnest
Well, its not as if i have not tried to use those solutions
I disagree that all but one solution answer showed a solution for any kind of updating. Here is why.
Accepted answer by HYRY:
I tried this answer originally, but I just don't know where you would input the z-coordinates, if you could kindly point it out to me. This answer is just so complicated. I do not even need the pointy arrow thing, I just need the point to be labelled there at all times, with the label following wherever the point goes. Besides, the text only updates on a mouse release, instead of dynamically updating the label. If you were to try the code that I have, you would realise that even if you drag the screen around, the points are still moving on their own, and the labels should as well.
Answer by msch:
I love this answer, its simple and straight to the core of the issue. This question is basically a build up from this answer. I guess my command of English is not as strong as yours, so I've edited the question once again to make sure that it says exactly what it is. Please let me know if it is still unclear.
Answer by Luchko:
This answer looks good, but is actually really complicated. I really can't even understand where to start for this. I've tried adding that class in, and then using the code
annotate3D(ax, s=str('hi'), xyz=xyz_, fontsize=10, xytext=(-3,3),textcoords='offset points', ha='right',va='bottom')
I got the error: TypeError: annotate3D() got multiple values for argument 's'
Answers by DonCristobal, fredcallaway and Rafael J:
Uses: fig.canvas.draw(), which there is no point to considering I'm using FuncAnimation. Wouldn't that defeat the purpose of using funcanimation if I were to use fig.canvas.draw()? Let me know if I'm wrong
The answer by duhaime:
There is no updating of text, it is just a simple slap there with ax.text.