I've got an animation with lines and now I want to label the points.
I tried plt.annotate() and I tried plt.text() but the labes don't move.
This is my example code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_line(num, data, line):
newData = np.array([[1+num,2+num/2,3,4-num/4,5+num],[7,4,9+num/3,2,3]])
line.set_data(newData)
plt.annotate('A0', xy=(newData[0][0],newData[1][0]))
return line,
fig1 = plt.figure()
data = np.array([[1,2,3,4,5],[7,4,9,2,3]])
l, = plt.plot([], [], 'r-')
plt.xlim(0, 20)
plt.ylim(0, 20)
plt.annotate('A0', xy=(data[0][0], data[1][0]))
# plt.text( data[0][0], data[1][0], 'A0')
line_ani = animation.FuncAnimation(fig1, update_line, 25, fargs=(data, l),
interval=200, blit=True)
plt.show()
Can you help me please?
My next step is:
I have vectors with origin in these Points. These vectors change their length and their direction in each animation step.
How can I animate these?
Without animation this works:
soa =np.array( [ [data[0][0],data[1][0],F_A0[i][0][0],F_A0[i][1][0]],
[data[0][1],data[1][1],F_B0[i][0][0],F_B0[i][1][0]],
[data[0][2],data[1][2],F_D[i][0][0],F_D[i][1][0]] ])
X,Y,U,V = zip(*soa)
ax = plt.gca()
ax.quiver(X,Y,U,V,angles='xy',scale_units='xy',scale=1)
First thanks a lot for your fast and very helpful answer!
My Vector animation problem I have solved with this:
annotation = ax.annotate("C0", xy=(data[0][2], data[1][2]), xycoords='data',
xytext=(data[0][2]+1, data[1][2]+1), textcoords='data',
arrowprops=dict(arrowstyle="->"))
and in the 'update-function' I write:
annotation.xytext = (newData[0][2], newData[1][2])
annotation.xy = (data[0][2]+num, data[1][2]+num)
to change the start and end position of the vectors (arrows).
But what is, wehn I have 100 vectors or more? It is not practicable to write:
annotation1 = ...
annotation2 = ...
.
:
annotation100 = ...
I tried with a list:
...
annotation = [annotation1, annotation2, ... , annotation100]
...
def update(num):
...
return line, annotation
and got this error:
AttributeError: 'list' object has no attribute 'axes'
What can I do? Have you any idea?
I'm coming here from this question, where an annotation should be updated that uses both xy and xytext. It appears that, in order to update the annotation correctly, one needs to set the attribute .xy of the annotation to set the position of the annotated point and to use the .set_position() method of the annotation to set the position of the annotation. Setting the .xytext attribute has no effect -- somewhat confusing in my opinion. Below a complete example:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
fig, ax = plt.subplots()
ax.set_xlim([-1,1])
ax.set_ylim([-1,1])
L = 50
theta = np.linspace(0,2*np.pi,L)
r = np.ones_like(theta)
x = r*np.cos(theta)
y = r*np.sin(theta)
line, = ax.plot(1,0, 'ro')
annotation = ax.annotate(
'annotation', xy=(1,0), xytext=(-1,0),
arrowprops = {'arrowstyle': "->"}
)
def update(i):
new_x = x[i%L]
new_y = y[i%L]
line.set_data(new_x,new_y)
##annotation.xytext = (-new_x,-new_y) <-- does not work
annotation.set_position((-new_x,-new_y))
annotation.xy = (new_x,new_y)
return line, annotation
ani = animation.FuncAnimation(
fig, update, interval = 500, blit = False
)
plt.show()
The result looks something like this:
In case that versions matter, this code has been tested on Python 2.7 and 3.6 with matplotlib version 2.1.1, and in both cases setting .xytext had no effect, while .set_position() and .xy worked as expected.
You have the return all objects that changed from your update function. So since your annotation changed it's position you should return it also:
line.set_data(newData)
annotation = plt.annotate('A0', xy=(newData[0][0],newData[1][0]))
return line, annotation
You can read more about matplotlib animations in this tutorial
You should also specify the init function so that the FuncAnimation knows which elements to remove from the plot when redrawing on the first update. So the full example would be:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Create initial data
data = np.array([[1,2,3,4,5], [7,4,9,2,3]])
# Create figure and axes
fig = plt.figure()
ax = plt.axes(xlim=(0, 20), ylim=(0, 20))
# Create initial objects
line, = ax.plot([], [], 'r-')
annotation = ax.annotate('A0', xy=(data[0][0], data[1][0]))
annotation.set_animated(True)
# Create the init function that returns the objects
# that will change during the animation process
def init():
return line, annotation
# Create the update function that returns all the
# objects that have changed
def update(num):
newData = np.array([[1 + num, 2 + num / 2, 3, 4 - num / 4, 5 + num],
[7, 4, 9 + num / 3, 2, 3]])
line.set_data(newData)
# This is not working i 1.2.1
# annotation.set_position((newData[0][0], newData[1][0]))
annotation.xytext = (newData[0][0], newData[1][0])
return line, annotation
anim = animation.FuncAnimation(fig, update, frames=25, init_func=init,
interval=200, blit=True)
plt.show()
I think I figured out how to animate multiple annotations through a list. First you just create your annotations list:
for i in range(0,len(someMatrix)):
annotations.append(ax.annotate(str(i), xy=(someMatrix.item(0,i), someMatrix.item(1,i))))
Then in your "animate" function you do as you have already written:
for num, annot in enumerate(annotations):
annot.set_position((someMatrix.item((time,num)), someMatrix.item((time,num))))
(You can write it as a traditional for loop as well if you don't like the enumerate way). Don't forget to return the whole annotations list in your return statement.
Then the important thing is to set "blit=False" in your FuncAnimation:
animation.FuncAnimation(fig, animate, frames="yourframecount",
interval="yourpreferredinterval", blit=False, init_func=init)
It is good to point out that blit=False might slow things down. But its unfortunately the only way I could get animation of annotations in lists to work...
Related
I have n curves that I draw using matplotlib's animation. Thanks to a previous question and the answer to it, this works well. Now I want to add some text in the plot which is continuously updated, basically the frame number, but I have no idea how to combine that object with the iterable of artists my animate function needs to return.
Here is my code:
import matplotlib.animation as anim
import matplotlib.pyplot as plt
import numpy as np
tracks = {}
xdata = {}
ydata = {}
n_tracks = 2
n_waypts = 100
for ii in range(n_tracks):
# generate fake data
lat_pts = np.linspace(10+ii*1,20+ii*1,n_waypts)
lon_pts = np.linspace(10+ii*.5,20+ii*.5,n_waypts)
tracks[str(ii)] = np.array( [lat_pts, lon_pts] )
xdata[str(ii)] = []
ydata[str(ii)] = []
fig = plt.figure()
ax1 = fig.add_subplot( 1,1,1, aspect='equal', xlim=(0,30), ylim=(0,30) )
plt_tracks = [ax1.plot([], [], marker=',', linewidth=1)[0] for _ in range(n_tracks)]
plt_lastPos = [ax1.plot([], [], marker='o', linestyle='none')[0] for _ in range(n_tracks)]
plt_text = ax1.text(25, 25, '')
def animate(i):
# x and y values to be plotted
for jj in range(n_tracks):
xdata[str(jj)].append( tracks[str(jj)][1,i] )
ydata[str(jj)].append( tracks[str(jj)][0,i] )
# update x and y data
for jj in range(n_tracks):
plt_tracks[jj].set_data( xdata[str(jj)], ydata[str(jj)] )
plt_lastPos[jj].set_data( xdata[str(jj)][-1], ydata[str(jj)][-1] )
plt_text.set_text('{0}'.format(i))
return plt_tracks + plt_lastPos
anim = anim.FuncAnimation( fig, animate, frames=n_waypts, interval=20, blit=True, repeat=False )
plt.show()
Simply changing the return statement to something like return (plt_tracks + plt_lastPos), plt_text or return (plt_tracks + plt_lastPos), plt_text, does not work. So how do I combine those artists correctly?
The animate function must return an iterable of artists (where an artist is a thing to draw as a result of a plot-call for example). plt_tracks is such an iterable, as well as plt_lastPost. plt_text, however, is a single artist. A possible solution to make the code work is thus changing the return statement to
return plt_tracks + plt_lastPos + [plt_text]
Alternatively, one could also write
return tuple(plt_tracks + plt_lastPos) + (plt_text,)
I'm doing a school project on evolutionary algorithms. I applied 2D particle swarm optimazation on an problem. I modified the PSO function such that I get as output:
a vector with the best parameters
the optimized solution
a list of array's that contains the x-values of the particles (lijst1)
a list of array's that contains the y-values of the particles (lijst2)
To visualise it, I wrote a loop that iterates through the list and plots the particles in 2D space. This not an elegant solution so I want to use animate from matplotlib.
I want to achieve that each frame shows an iteration of the PSO algorithm or in other words each frame should plot lijst1[i] and lijst2[i].
To give you guys an idea of the output I ran the algorithm a few iterations with few particles.
Lijst1:
[array([-3. , -1.03182383, -1.29765138, -2.63259751, 1.83881941]), array([-3. , -0.99308702, -1.61309168, -1.99811905, 1.31329269]), array([-2.26835254, -1.00436484, -1.48864562, -1.35639631, -0.49339506]), array([-1.89641446, -1.05791927, -1.22609973, -0.94357677, -1.416652 ])]
Lijst2:
[array([ 6. , 6. , 2.25620012, -0.49762621, 1.20955011]), array([ 5.39456692, 5.66213386, 1.53168852, -0.23988646, 1.97288628]), array([3.70817355, 4.68329424, 1.37540136, 0.88142455, 1.08398406]), array([2.60343502, 2.80393532, 1.67560348, 2.0280248 , 0.29435041])]
I used the sample code from the matplotlib website and modified it. Here is where I need help:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
%matplotlib notebook
n = 4
fig, ax = plt.subplots()
xdata, ydata = [], []
ln, = plt.plot([], [], 'ro')
def init():
ax.set_xlim(-1,1)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
xdata.append(lijst1[frame])
ydata.append(lijst2[frame])
ln.set_data(xdata, ydata)
return ln,
ani = FuncAnimation(fig, update, frames=np.linspace(0,n-1,n), interval=1000, init_func=init, blit=True)
plt.show()
What I first wanted to achieve is to plot all the frames in the same figure. The lijst1 and lijst2 have 4 indices. So I thought let the frames denote [0,1,2,3] so that each frame lijst1[i] and lijst[i] get appended to the data. In the end probably my whole figure will be filled with particles, but that is okay for now.
For some reason I don't get input at all. I'm not sure why this is. Could I please get some feedback on this?
Question: How do I modify the animation function such that I visualise the convergence process of the PSO algorithm?
Thanks in advance
I wrote the code with the understanding that your question is to loop through the contents of the list data presented to you. The change from your code is to set the range of the axes. I also removed the initialization function as I thought it was unnecessary. I added a line from the scatter plot format as a graph setting. This is just a modification to check the animation.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
%matplotlib notebook
# from IPython.display import HTML
import numpy as np
from matplotlib.animation import PillowWriter
lijst1 = [np.array([-3. , -1.03182383, -1.29765138, -2.63259751, 1.83881941]), np.array([-3. , -0.99308702, -1.61309168, -1.99811905, 1.31329269]), np.array([-2.26835254, -1.00436484, -1.48864562, -1.35639631, -0.49339506]), np.array([-1.89641446, -1.05791927, -1.22609973, -0.94357677, -1.416652 ])]
lijst2 = [np.array([ 6. , 6. , 2.25620012, -0.49762621, 1.20955011]), np.array([ 5.39456692, 5.66213386, 1.53168852, -0.23988646, 1.97288628]), np.array([3.70817355, 4.68329424, 1.37540136, 0.88142455, 1.08398406]), np.array([2.60343502, 2.80393532, 1.67560348, 2.0280248 , 0.29435041])]
n = 4
fig, ax = plt.subplots()
ax = plt.axes(xlim=(-5,5), ylim=(-2, 10))
xdata, ydata = [], []
ln, = plt.plot([], [], 'ro--', lw=2)
def init():
ax.set_xlim(-1,1)
ax.set_ylim(-1, 1)
return ln,
def update(frame):
# xdata.append(lijst1[frame])
# ydata.append(lijst2[frame])
xdata = lijst1[frame].tolist()
ydata = lijst2[frame].tolist()
ln.set_data(xdata, ydata)
return ln,
ani = FuncAnimation(fig, update, frames=len(lijst1), interval=1000, blit=True)# init_func=init,
plt.show()
ani.save('swarm_ani.gif', writer='pillow')
# plt.close()
# HTML(ani.to_html5_video())
I am trying to create a program that can visualize the change of a portfolio in real time. To do this, I update my data and create a new plot with it. When I run the code below in PyCharm, SciView stops displaying the plots after 30 iterations. Ideally, I would like to have it only show the most recent plot, but it would also be fine if it just truncated the history so that I at least always see the current plot. Is there any way to do this? I tried different ways to close the figures (e. g. using plt.close()), but did not achieve the desired result.
Code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def create_plot(self):
"""
Takes an x and a y (both 1D arrays and constructs a plot from it)
:return: a pyplot figure object
"""
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Draw x and y lists
ax.clear()
ax.plot(self.x, self.y)
# Format plot
plt.xticks(rotation=90)
plt.title('Portfolio')
plt.ylabel('Value')
plt.show()
plt.close('all')
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.create_plot()
Rather than creating a new plot and window every time, you can also update the current Matplotlib figure data in each iteration. You then need to view the plot in an interactive Matplotlib environment.
Live updating Matplotlib plots
You can use code similar to this to update the data inside the plot:
import matplotlib.pyplot as plt
import random
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
# Variables for our updating data
x = []
y = []
for i in range(50):
# Generate random data
x.append(i)
y.append(random.random())
# Update the plot with the new x, y data
ax.plot(x, y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
Allow for interactive Matplotlib mode when using SciView
Deactivate SciView or manually set your backend to another interactive GUI to see the updating plot.
This code snipped automatically chooses the correct backend (same list as in the Matplotlib code):
import matplotlib.pyplot as plt
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
Applied to your code
Your code with suggested modifications would look like this:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def update_plot(self, fig, ax):
import _tkinter
try:
ax.plot(self.x, self.y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
# Capture an error in case the plotting window is being closed
except _tkinter.TclError:
pass
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
# Choose the right backend
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
# Create plot
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.update_plot(fig, ax) # Update the plot the new data
Same issue here.
The workaround I found is to change the matplotlib backend to plot outside the PyCharm.
import matplotlib
matplotlib.use('qt5Agg')
matplotlib.pyplot.ioff()
Then you have to explicit open a new figure and show
for i in range(100):
plt.figure()
...
...
plt.show()
I'm animating a scatter plot with a code below. It reads data from a .csv file "sample.csv" and animates it by using np.roll in update function.
import numpy as np
import matplotlib.pyplot as plt
from numpy import genfromtxt
from matplotlib.animation import FuncAnimation
my_data = genfromtxt("sample.csv", delimiter=",", skip_header=1) # reading data
fig, ax = plt.subplots()
xdata, ydata = [], []
line, = ax.plot([], [], ".", markersize=14)
plt.grid()
def init():
ax.set_xlim(0, 50)
ax.set_ylim(0, 60)
return line,
def update(frame):
my_data[:,0] = np.roll(my_data[:,0],1) # moving graph
gap_loc = [19,20,21] # location of a gap
my_data[gap_loc, 1] = np.nan # creating a gap in graph
xdata.append(my_data[:,0])
ydata.append(my_data[:,1])
line.set_data(xdata, ydata)
return line,
ani = FuncAnimation(fig, update, frames=np.arange(0,50,1), init_func=init, blit=True)
plt.show()
The result looks like that:
As you see there is a gap which moves together with the remaining points. However, what I want to achieve is that the gap was stationary at the locations on the horizontal axis: 19,20,21.
How can I achieve this effect?
Below please find a dataset, I'm using for this animation.
Day,Var 1,Var 2
1,2,12
2,4,19
3,6,20
4,8,25
5,10,25
6,12,33
7,14,40
8,16,47
9,18,49
10,20,50
11,22,52
12,24,55
13,26,65
14,28,82
15,30,100
16,32,100
17,34,110
18,36,117
19,38,140
20,40,145
21,42,164
22,44,170
23,46,198
24,48,200
25,50,210
26,48,210
27,46,211
28,44,216
29,42,267
30,40,317
31,38,325
32,36,335
33,34,337
34,32,347
35,30,356
36,28,402
37,26,410
38,24,448
39,22,449
40,20,457
41,18,463
42,16,494
43,14,500
44,12,501
45,10,502
46,8,514
47,6,551
48,4,551
49,2,558
50,0,628
Define the gap when you load the data, and do so in the x column rather than the y:
# imports
my_data = genfromtxt("sample.csv", delimiter=",", skip_header=1) # reading data
gap_loc = [19,20,21] # location of a gap
my_data[gap_loc, 0] = np.nan # creating a gap in graph
# plotting code
So now when you roll the x column, there will always be np.nan at the x values [19, 20, 21], regardless of what the y coordinate is. You can use print(my_data) within the update function to make clear what was going on each iteration.
Here is the result:
Also, I think you are over-plotting because you continually expand xdata and ydata using append. I ended up just removing the xdata and ydata and doing:
def update(frame):
my_data[:,0] = np.roll(my_data[:,0],1) # moving graph
line.set_data(my_data[:,0], my_data[:,1])
return line,
I have a dictionary which is updated by a for loop , I am trying to plot the key value pairs in a plot which moves or slides as the number of key value pairs keep updating and the plot just shows the 50 current values from the dictionary.
so far I have made :
for k,v in plot.items():
plt.barh(k,v , color='blue')
plt.pause(0.3)
plt.show()
The problem is that the values keep appending and the plots keeps growing. Is there any way to show just last 50 k,v pairs from a dictionary and and keep on updating the plot. I have also tried a function :
def live_plotter(x_vec,y1_data,line1,identifier='',pause_time=0.1):
if line1==[]:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
#fig = plt.figure(figsize=(13,6))
#ax = fig.add_subplot(111)
# create a variable for the line so we can later update it
line1, = plt.plot(x_vec,y1_data,'-o', alpha=0.8)
#update plot label/title
plt.ylabel('Cross-correlation')
plt.title('Title: {}'.format(identifier))
line1.set_data(x_vec, y1_data)
plt.pause(pause_time)
return line1
But this also doesn't update the plot and just appends to the plot like the code above.
I have also tried the animation function :
fig = plt.figure()
def animate():
for k, v in plot:
print(k, v)
plt.barh(k, v, color='blue')
plt.pause(0.3)
ani = matplotlib.animation.FuncAnimation(fig, animate, interval=1000)
plt.show()
Reproducible example:
import matplotlib.pyplot as plt
import matplotlib.animation
plot_base={'00002200': 986.11152642551519,
'00002201': 989.90915858383369,
'00002202': 992.87095144990781,
'00002203': 994.89971071876084,
'00002204': 992.92424216660561,
'00002205': 993.19845750930426,
'00002206': 988.88001766153957,
'00002207': 988.95062195981848,
'00002208': 993.17138084871465,
'00002209': 993.9863425202193,
'00002210': 993.43551440410283,
'00002211': 993.04540624076844,
'00002212': 991.40048097057559,
'00002213': 993.16124311517319,
'00002214': 994.06785011666204,
'00002215': 985.24294182260996,
'00002216': 990.5369409623512,
'00002217': 991.83512034302737,
'00002218': 993.43756392913269,
'00002219': 989.77919409784511,
'00002220': 988.09683378239572,
'00002221': 992.19961090836773,
'00002222': 992.69477342507912,
'00002223': 992.37890673842412,
'00002224': 991.55520651752556,
'00002225': 992.15414070360941,
'00002226': 991.49292821478309,
'00002227': 994.75013161999084,
'00002228': 991.54858727670728,
'00002229': 993.22846583401292,
'00002230': 993.88719133150084,
'00002231': 992.89934842358855,
'00002232': 991.10582582918869,
'00002233': 993.24750746833467,
'00002234': 992.6478137931806,
'00002235': 991.2614284514957,
'00002236': 994.38800336488725}
plot={}
for k,v in plot_base.items():
plot.update({k: v})
for k,v in plot.items():
bar, = plt.bar(k, v, color='blue')
plt.pause(0.3)
plt.plot()
'''
def animate(i):
bars = []
for k, v in plot.items():
bar, = plt.bar(k, v, color='blue')
bars.append(bar)
return bars
fig=plt.figure(figsize=(12 ,7))
ani = matplotlib.animation.FuncAnimation(fig, animate, interval=1000, blit=True)
plt.show()
'''
When we run this code the values from the dictionary keep appending to the x-axis , that's why I want to make it scroll able(auto-scroll) , The triple quoted code shows the animation part, this make the whole graph appear at once.
I don't think I understand the data structure used here, so this focuses on the animating part. Suppose you have a function store.get_last_20_values from which to obtain the data you want to plot. Then the animation would look like:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig, ax = plt.subplots()
def animate(i):
cat, val = store.get_last_20_values()
ax.clear()
ax.barh(cat, val)
ani = FuncAnimation(fig, animate, interval=1000)
plt.show()
For completeness here is how that function could look like, to make the above runnable.
class Store():
x = []
y = []
def update(self):
n = np.random.randint(0,5)
x = np.random.randint(100000,999999, size=n).astype(str)
y = np.random.randint(50,999, size=n)
self.x.extend(x)
self.y.extend(y)
self.x = self.x[-20:]
self.y = self.y[-20:]
def get_last_20_values(self):
# Some function to return 20 values. Need to adapt to custom needs.
self.update()
return self.x, self.y
store = Store()