Reputation: 43
I recently wrote this to scrape a log and show a matplotlib.pyplot.bar
plot of the most used words in it
import re
from datetime import datetime
from collections import Counter
import matplotlib.pyplot as plt
from matplotlib import animation
def read_log(path, index, separator=chr(9)):
data = []
my_file = open(path,"r+")
rows = my_file.readlines()
for row in rows:
line = re.sub(r'\r\n|\r|\n','',row, flags=re.M)
if line != '':
data.append(line.split(separator)[index])
my_file.close()
return Counter(data)
def set_plot(counter_data):
plt.title('This is a title')
plt.bar(range(len(counter_data)), list(counter_data.values()), align='center')
plt.xticks(range(len(counter_data)), list(counter_data.keys()))
plt.tight_layout()
plt.show()
counter_data = read_log(r'logfile.txt',2)
print(counter_data)
set_plot(counter_data)
I would love to animate said plot, however, I can't grasp animation.FuncAnimation()
Can you help me out?
I added these lines:
fig = plt.Figure()
animation.FuncAnimation(fig, set_plot(counter_data), frames=20)
and deleted plt.show()
So I could give FuncAnimation an empty figure (fig) and the function. But it doesn't work. EDIT: And it doesn't print an error either.
Upvotes: 3
Views: 1845
Reputation: 425
It seems your data is static (you get it from file once and it doesn't change), so I don't really understand what you are trying to animate. But, your code contains errors that need to be fixed, so for demonstration purposes I will add increment each of the heights in each step of animation.
The first mistake is in the way you pass arguments to your function. For arguments you have to use fargs
parameter, otherwise in your version you are passing the result of function not the function itself.
You must have a function (animate
in my version, set_plot
in yours) that updates the plot for each step of your animation. (in your case you just put the same data every time)
That function needs to accept at least one parameter (val
) which is used my FuncAnimation
which passes values got from iterator passed to its frames
parameter.
The final code looks like this
import re
from datetime import datetime
from collections import Counter
import matplotlib.pyplot as plt
from matplotlib import animation
# uncomment if using in jupyter notebook
# %matplotlib nbagg
def read_log(path, index, separator=chr(9)):
data = []
my_file = open(path,"r+")
rows = my_file.readlines()
for row in rows:
line = re.sub(r'\r\n|\r|\n','',row, flags=re.M)
if line != '':
data.append(line.split(separator)[index])
my_file.close()
return Counter(data)
fig = plt.figure()
ax = fig.add_subplot()
counter_data = read_log(r'tmp.csv',2)
plt.title('This is a title')
bar = ax.bar(range(len(counter_data)), list(counter_data.values()), align='center')
plt.xticks(range(len(counter_data)), list(counter_data.keys()))
plt.tight_layout()
plt.ylim((0, 30))
def animate(val, counter_data):
data = list(counter_data.values())
for i in range(len(data)):
bar[i].set_height(data[i]+val)
animation.FuncAnimation(fig, func=animate, frames=20, fargs=[counter_data], save_count=10)
and we get the following animation:
Edit:
For errors you can try to save your animation to gif, and the errors will show up
anim = animation.FuncAnimation(fig, func=animate, frames=20, fargs=[counter_data], save_count=10)
anim.save('anim.gif', 'imagemagick')
Upvotes: 2
Reputation: 5148
The main problem is that FuncAnimation
expects a callable which returns artist objects. The callable will be called repeatedly with a frame argument.
In your example, set_plot()
is called once. It's return value (None
) is passed to FuncAnimation
. Instead you should have a method, e.g. update_plot()
, which loads the data from the file, updates the bar plot and returns the bar plot. This function (the function itself) should be passed to FuncAnimation
animation.FuncAnimation(fig, update_plot, frames=20)
without calling it! Note the missing parenthesis after update_plot
. The animitation documentation shows examples how this can be done.
Upvotes: 0