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.
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')
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.
Related
I am working on a project that requires me to log data over time, while also plotting the data on screen with a live line graph. I have gotten everything but the line graph to work this far and am unsure what I am doing incorrectly. This is the imports that I am currently using.
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import pyplot as plt
from matplotlib import style
from tkinter import *
from PIL import Image
import numpy as np
import serial
from serial import Serial
import sqlite3
import time
from datetime import datetime
from array import *
import cv2
from pathlib import Path
from itertools import count
The data that is meant to be used for the Y axis plotting is stored in an array of data. Each index in this array is to hold the last read value from the sensors, i=0 is sensor 1 and so on.
A=[0,0,0,0,0,0,0,0]
This is the definition of the subplot that I am trying to draw to. I think I am setting this up correctly, however I am not getting the expected result so likely not.
fig1 = plt.Figure(dpi=100, facecolor="#f0f0f0")
a = fig1.add_subplot(111)
a.patch.set_facecolor("#f0f0f0")
a.set_xlabel('time (Sec)')
a.set_ylabel('pressure(kPa)')
a.set_ylim(0,100)
a.set_xlim(0,30)
graph1 = FigureCanvasTkAgg(fig1, master=root)
graph1.get_tk_widget().place(x=10, y=220, width=210, height=280)
graph1.draw();
I am currently just trying to get one of the lines to draw first before handling the, seemingly, minor issue that is overlapping multiple lines. This is the function that I am trying to use in order to draw said line.
def graph_plotdata():
global A
global a
line1 = []
time = []
time.append(next(index))
line1.append(A[0])
a.cla()
a.plot(time, line1)
graph1.draw()
I have tried several iterations of this code in order attempt to solve this problem. The closest I have to getting it to work is in the current state in which something is happening however instead of keeping my min and max limits on the graph it completely reformats my plot and plots an "invisible" line.
Before starting:
After starting:
I am not overwhelmingly experienced when is comes to python libraries so bare with me.
I use a dictionary to store the various lines and line plots and then update the plots using set_data(xdata, ydata). I'm not sure how your datastream works, so mine just updates when I push the update button and generates a random reading. You'll obviously want to change those parts to match your data input.
fig, ax = plt.subplots(1, 1)
plt.subplots_adjust(bottom = 0.20)
num_sensors = 10
latest_reading = [0]*num_sensors
lines = {index: [0] for index in range(num_sensors)}
times = [0]
line_plots = {index: ax.plot(lines[index])[0] for index in range(num_sensors)}
btn_ax = plt.axes([0.475, 0.05, 0.10, 0.05])
def update(event):
latest_reading = np.random.randint(0, 10, num_sensors)
times.append(times[-1] + 1)
for index in range(num_sensors):
lines[index].append(latest_reading[index])
line_plots[index].set_data(times, lines[index])
# Adjust limits
max_time_window = 20
ax.set_xlim(max(0, max(times)-max_time_window), max(times))
ax.set_ylim(0, max(lines))
plt.draw()
btn = mpl.widgets.Button(btn_ax, 'Update')
btn.on_clicked(update)
Thank you for the response.
I figured out the issue, it had nothing to do with my matplotlib/tkinter implementation. I just totally missed that I had a scope inheritance issue. The lists of 'time' and 'line1' are not persistent in the entire scope and therefore being rewritten to empty lists every time the 'graph_plotdata()' function is called.
my solution is as follows:
timet = []
line1 = []
"""----------Graph Updater-----------"""
def graph_plotdata():
global B
global a
global graph1
global timet
global line1
timet.append(next(index))
line1.append(B[0])
a.clear()
a.plot(timet, line1)
a.patch.set_facecolor("#f0f0f0")
a.set_xlabel('time (Sec)')
a.set_ylabel('pressure(kPa)')
a.set_ylim(0,30)
a.set_xlim(0,30)
graph1.draw()
Hopefully this helps people in the future running into a similar issue!
My task is to plot a numpy array in real time using matplotlib. Please note that I don't want to use animation function to do this.
import numpy as np
import time
from matplotlib.lines import Line2D
import matplotlib
class Plot:
def __init__(self,f,axis,data):
self.fig = f
self.axis = axis
self.data = data
def plotting(self,i):
xs = [self.data[i,0],self.data[i+1,0]]
ys = [self.data[i,1],self.data[i+1,1]]
line, = self.axis.plot(xs,ys,'g-')
self.fig.canvas.draw()
data = np.random.rand(10,2) #numpy array
f = plt.figure()
axis = f.add_axes([0,0,0.9,0.9])
plotData = Plot(f,axis,data)
for i in range(len(data)-1):
plotData.plotting(i)
time.sleep(1)
plt.show()
But everytime I run this code it returns me one empty figure. How do I rectify it?
import matplotlib.pyplot as plt
import numpy as np
# use ggplot style for more sophisticated visuals
plt.style.use('ggplot')
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, = ax.plot(x_vec,y1_data,'-o',alpha=0.8)
#update plot label/title
plt.ylabel('Y Label')
plt.title('Title: {}'.format(identifier))
plt.show()
# after the figure, axis, and line are created, we only need to update the y-data
line1.set_ydata(y1_data)
# adjust limits if new data goes beyond bounds
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1
A few notes on the function above:
line1.set_ydata(y1_data) can also be switched to line1.set_data(x_vec,y1_data) to change both x and y data on the plots.
plt.pause() is necessary to allow the plotter to catch up - I've been able to use a pause time of 0.01s without any issues
The user will need to return line1 to control the line as it is updated and sent back to the function
The user can also customize the function to allow dynamic changes of title, x-label, y-label, x-limits, etc.
This Python program plots lines dynamically from data in a csv file. When the program first starts it dynamically draws points that already exist in the file. This part works as expected. I'd like for any new points added to the file to be subsequently drawn. The problem is that i continues to increment so by the time a new item is added to my csv file the value of i is usually much higher than the index from the csv so it never gets plotted. How can I prevent the count of i continuing on until there is an applicable value in the csv file?
import numpy as np
from itertools import count
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
plt.style.use('fivethirtyeight')
data = pd.read_csv('csv_data.csv')
x_vals = []
y_vals1 = []
y_vals2 = []
index = count()
def animate(i):
x = data['x_value']
y1 = data['total_1']
y2 = data['total_2']
x_vals.append(x[i])
y_vals1.append(y1[i])
y_vals2.append(y2[i])
plt.cla()
plt.plot(x_vals, y_vals1, label='Channel 1')
plt.plot(x_vals, y_vals2, label='Channel 2')
plt.legend(loc='upper left')
plt.tight_layout()
ani = FuncAnimation(plt.gcf(), animate, interval=100)
plt.show()
I can't figure how to plot a continuous function using Matplotlib. I got how to plot a scatterplot, but I would like a continuous plot.
Here is my code:
import matplotlib.pyplot as plt
from matplotlib.pyplot import autoscale
import matplotlib.animation as animation
import numpy
class MyPlot():
def __init__(self):
self.index = 0
self.setup()
def setup(self):
plt.ion()
self.fig, self.ax = plt.subplots()
self.line = self.ax.plot([],[])
autoscale()
plt.show()
def anim(self, i):
self.line.set_ydata(i) # update the data
return self.line,
def add(self, val):
print self.index, val
self.ax.plot(self.index, val)
animation.FuncAnimation(self.fig, self.anim, repeat=False)
plt.pause(0.05)
#if(self.index >= ntests):
self.index+=1
if __name__== "__main__":
import time
from random import random
p = MyPlot()
for i in range(100):
p.add(random())
time.sleep(0.5)
This works, but doesn't draw anything. The plot resizes itself, though.
You are only plotting a line with a single point at a time (which doesn't exist), so nothing shows up. If you replace self.ax.plot with self.ax.scatter, it plots correctly.
If you really want lines, you can just keep track of the last index and value and plot a line connecting the last index and value with the current index and value each time.
Add these two lines to add()
self.ax.plot([self.index-1, self.index], [self.lastval, val])
self.lastval = val
as well as a line initializing self.lastval to numpy.nan in setup()
You can actually append values to a line plot in matplotlib:
self.line.set_xdata(numpy.append(self.line.get_xdata(), self.index))
self.line.set_ydata(numpy.append(self.line.get_ydata(), val))
This way, you do not have to do any of the bookkeeping yourself.
More details can be found at https://stackoverflow.com/a/10944967/2988730
I'm using Panda and matplotlib to draw graphs in Python.
I would like a live updating gaph. Here is my code:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
import numpy as np
import MySQLdb
import pandas
def animate():
conn = MySQLdb.connect(host="localhost", user="root", passwd="", db="sentiment_index", use_unicode=True, charset="utf8")
c = conn.cursor()
query = """ SELECT t_date , score FROM mytable where t_date BETWEEN Date_SUB(NOW(), Interval 2 DAY) AND NOW()"""
c.execute(query)
rows=c.fetchall()
df = pandas.read_sql(query, conn, index_col=['t_date'])
df.plot()
plt.show()
animate()
I thought about using FuncAnimation but didn't get the right result. Any help please?
The documentation is a bit light on explanation of how to use
FuncAnimation. However, there are examples in the
gallery and blog
tutorials, such as Jake Vanderplas's and Sam Dolan's PDF.
This example from Jake Vanderplas's tutorial is perhaps the "Hello World" of
matplotlib animation:
from __future__ import division
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def init():
return [line]
def animate(i, ax, line):
x = np.linspace(0, 2*np.pi, N) + i/(N*2)
ax.set_xlim(x.min(), x.max())
line.set_data(x, np.sin(x))
return [line]
N = 100
fig, ax = plt.subplots()
line, = ax.plot([], [])
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1, 1)
ani = animation.FuncAnimation(
fig, animate, init_func=init, interval=0, frames=int(4*np.pi*N),
repeat=True, blit=True, fargs=[ax, line])
plt.show()
Change various values or lines of code and see what happens. See what happens if
you change return [line] to something else. If you study and play with these
examples, you can learn how the pieces fit together.
Once you understand this example, you should be able to modify it to fit your
goal.
If you have trouble, post your code and describe what error message or
misbehavior you see.
Some tips:
Since animation requires calling line.set_data, I don't think you
can use Pandas' df.plot(). In fact, I'm not sure if the Pandas DataFrame is
useful here. You might be better off sucking the data into lists or NumPy arrays
and passing those to line.set as above, without getting Pandas involved.
Opening a connection to the database should be done once. animate gets
called many times. So it is better to define conn and c and query -- anything that does not change with each call to animate --
outside of animate, and pass them back as arguments to animate via the
fargs parameter.