Generating repeatedly updating graph (FuncAnimation - Matplotlib) - python

I am trying to write a code that will generate a graph that is being repeatedly updated and has twin axes (2 y-axis, sharing the same x-axis).
The code works well when I don't combine it with FuncAnimation, however when I try to do that I get an empty graph.
def animate(i):
data=prices(a,b,c) #function that gives a DataFrame with 2 columns and index
plt.cla()
fig=plt.figure()
ax = fig.add_subplot(111)
ax.plot(data.index, data.value1)
ax2 = ax.twinx()
ax2.plot(data.index, data.value2)
plt.gcf().autofmt_xdate()
plt.tight_layout()
call = FuncAnimation(plt.gcf(), animate, 1000)
plt.tight_layout()
plt.show
'''
I believe the error is in "call". Unfortunately, I don't know FuncAnimation so well.

You can try something like this:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pandas as pd
from datetime import datetime, timedelta
def getPrices(i):
return pd.DataFrame(index=[datetime.now() + timedelta(hours=i) for i in range(10)], data={'value1':range(10), 'value2':[(x + i) % 5 for x in range(10)]})
def doAnimation():
fig=plt.figure()
ax = fig.add_subplot(111)
def animate(i):
#data=prices(a,b,c) #function that gives a DataFrame with 2 columns and index
data = getPrices(i)
plt.cla()
ax.plot(data.index, data.value1)
ax2 = ax.twinx()
ax2.plot(data.index, data.value2)
plt.gcf().autofmt_xdate()
plt.tight_layout()
return ax, ax2
call = FuncAnimation(plt.gcf(), animate, 1000)
plt.show()
doAnimation()
UPDATE:
Though this works in my environment, OP in a comment indicated it doesn't work and the following warning is raised:
UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g. anim, that exists until you have outputted the Animation using plt.show() or anim.save()
As plt.show() is called immediately after the call to FuncAnimation(), this is puzzling, but perhaps the following will help to ensure the Animation does not get deleted prematurely:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pandas as pd
from datetime import datetime, timedelta
def getPrices(i):
return pd.DataFrame(index=[datetime.now() + timedelta(hours=i) for i in range(10)], data={'value1':range(10), 'value2':[(x + i) % 5 for x in range(10)]})
def doAnimation():
fig=plt.figure()
ax = fig.add_subplot(111)
def animate(i):
#data=prices(a,b,c) #function that gives a DataFrame with 2 columns and index
data = getPrices(i)
plt.cla()
ax.plot(data.index, data.value1)
ax2 = ax.twinx()
ax2.plot(data.index, data.value2)
plt.gcf().autofmt_xdate()
plt.tight_layout()
return ax, ax2
call = FuncAnimation(plt.gcf(), animate, 1000)
return call
callSave = doAnimation()
plt.show()

Related

Using Python's Matplotlib how can I align annotation updates for specific points to my data set as it is graphed using FuncAnimation?

I have the following code that functions perfectly with the exception that the timing of placing my point annotations and updating the legend does not align to when the point appears on the graph. How do I get them to align?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib import style
import matplotlib.patches as mpatches
style.use('ggplot')
x_data = [1,5,7,9,11,12,14,15,27,29,37,39,45,47,52,53,57,58,61,62,66,80,82,83,84,85,90,91,93,96,98,105,109,110,111,113,114,116,117,120,122,123,127,134,136,138,140,141,144,160,161,162,165,174,176,179,183,184,185,186,190,191,192,193,194,195,199,200,204]
y_data = [50,55,40,30,31,20,21,18,25,21,15,18,20,24,27,30,32,35,37,38,30,11,13,10,10,14,16,18,19,17,14,9,9,4,5,5,6,5,7,3,6,8,10,13,15,12,10,13,8,4,3,5,4,5,7,6,4,8,10,12,10,12,12,12,12,15,17,18,18]
annotations = ['','','Pasted Value','Cut Value','','Pasted Cut Value','','Abnormal Pause','','Abnormal Pause','Out of Order Field Interaction','','','','','','','','','','Window Exit','Window Entry','','Pasted Value','Out of Order Field Interaction','','','Irregular Typing Cadence','','Irregular Typing Cadence','Abnormal Pause','Irregular Typing Cadence','Out of Order Field Interaction','Value Manipulation','','Out of Order Field Interaction','','Value Manipulation','','Value Manipulation','','','','','','','','','Window Exit','Window Entry','Pasted Value','','Value Manipulation','','','Value Manipulation','Value Manipulation','','','','Copied Value','','Frustration - Repeat Paste','Frustration - Repeat Paste','Frustration - Repeat Paste','','','','']
print(len(x_data))
print(len(y_data))
print(len(annotations))
fig, ax = plt.subplots()
ax.set_xlim(0,205)
ax.set_ylim(0,100)
line, = ax.plot(0,50)
def init():
line.set_data([], [])
return line,
def animate(n):
line, = plt.plot(x_data[:n], y_data[:n], color='b', marker='o')
ax.annotate(annotations[n],(x_data[n],y_data[n]))
updated_score = mpatches.Patch(color='b', label=y_data[n])
plt.legend(handles=[updated_score])
return line,
animation = FuncAnimation(fig, animate, frames=len(x_data), interval=500)
plt.title('A Cool Title')
plt.xlabel('Time in Seconds')
plt.ylabel('Value')
plt.show()
Here is a working replit
I think the mix of ax and plot is the issue, I rewrote it in object style and fixed the code.
fig, ax = plt.subplots()
ax.set_xlim(0,205)
ax.set_ylim(0,100)
line, = ax.plot(0,50)
ax.set_title('A Cool Title')
ax.set_xlabel('Time in Seconds')
ax.set_ylabel('Value')
def init():
line.set_data([], [])
return line,
def animate(n):
line, = ax.plot(x_data[:n], y_data[:n], color='b', marker='o')
ax.annotate(annotations[n],(x_data[n],y_data[n]))
updated_score = mpatches.Patch(color='b', label=y_data[n])
ax.legend(handles=[updated_score])
return line,
animation = FuncAnimation(fig, animate, frames=len(x_data), interval=500, repeat=False)
# animation.save('line_legend_sync_ani.gif', writer='pillow')
plt.show()
Remember that when slicing Pandas doesn't include the last value, therefore in this line plt.plot(x_data[:n+1], y_data[:n+1]) you should add +1 to include the n-th value. I think that solve the lack of timming.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib import style
import matplotlib.patches as mpatches
style.use('ggplot')
x_data = [1,5,7,9,11,12,14,15,27,29,37,39,45,47,52,53,57,58,61,62,66,80,82,83,84,85,90,91,93,96,98,105,109,110,111,113,114,116,117,120,122,123,127,134,136,138,140,141,144,160,161,162,165,174,176,179,183,184,185,186,190,191,192,193,194,195,199,200,204]
y_data = [50,55,40,30,31,20,21,18,25,21,15,18,20,24,27,30,32,35,37,38,30,11,13,10,10,14,16,18,19,17,14,9,9,4,5,5,6,5,7,3,6,8,10,13,15,12,10,13,8,4,3,5,4,5,7,6,4,8,10,12,10,12,12,12,12,15,17,18,18]
annotations = ['','','Pasted Value','Cut Value','','Pasted Cut Value','','Abnormal Pause','','Abnormal Pause','Out of Order Field Interaction','','','','','','','','','','Window Exit','Window Entry','','Pasted Value','Out of Order Field Interaction','','','Irregular Typing Cadence','','Irregular Typing Cadence','Abnormal Pause','Irregular Typing Cadence','Out of Order Field Interaction','Value Manipulation','','Out of Order Field Interaction','','Value Manipulation','','Value Manipulation','','','','','','','','','Window Exit','Window Entry','Pasted Value','','Value Manipulation','','','Value Manipulation','Value Manipulation','','','','Copied Value','','Frustration - Repeat Paste','Frustration - Repeat Paste','Frustration - Repeat Paste','','','','']
print(len(x_data))
print(len(y_data))
print(len(annotations))
fig, ax = plt.subplots()
ax.set_xlim(0,205)
ax.set_ylim(0,100)
line, = ax.plot(0,50)
def init():
line.set_data([], [])
return line,
def animate(n):
line, = plt.plot(x_data[:n+1], y_data[:n+1], color='b', marker='o')
ax.annotate(annotations[n],(x_data[n],y_data[n]))
updated_score = mpatches.Patch(color='b', label=y_data[n])
ax.legend(handles=[updated_score])
return line,
animation = FuncAnimation(fig, animate, frames=len(x_data), interval=500)
plt.title('A Cool Title')
plt.xlabel('Time in Seconds')
plt.ylabel('Value')
plt.show()
Check here the result: https://repl.it/#JuanJavier1/A-Cool-Title

How to show only 'x' amount of values on a graph in python

I am new to python and am carrying out some little projects along side watching tutorials to enable me to learn.
I have recently been working with some APIs to collect data - I save this data in a CSV file and then open the CSV file to show the data as a graph.
I want the graph to show the data LIVE, but in doing so I only want 10 values on the screen at once, so when the 11th value is plotted, the 1st is no longer visible unless the scrolling function is used to look back at it..
I have managed to pull together the code that plots the live data from the CSV file, as well as some code that creates the graph in the desired format - but as I am quite new to python I am unsure of how I'd make them work together.. Any advice would be greatly appreciated.
Below is the code that I have created to read and plot from a CSV file:
import random
from itertools import count
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
index = count()
def animate(i):
data = pd.read_csv('x.csv')
x = data['Time']
y = data['R1Temp']
y1 = data['R2Temp']
y2 = data['R3Temp']
plt.cla()
plt.plot(x, y, marker = 'o', label='Room 1 Temp')
plt.plot(x, y1, marker = 'o', label='Room 2 Temp')
plt.plot(x, y2, marker = 'o', label='Room 3 Temp')
plt.xlabel("Time")
plt.ylabel("Temperature °C")
plt.title("Live temperature of Rooms")
plt.legend(loc='upper left')
plt.tight_layout()
ani = FuncAnimation(plt.gcf(), animate, interval=1000)
plt.tight_layout()
plt.show()
Below is the code that shows the way in which I'd like the graph to format the data plots:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update(frame):
global x, y
start = x[max(frame-PAN//2, 0)]
start = x[max(frame-PAN+1, 0)]
end = start + PAN
ax.set_xlim(start, end)
start, end = ax.get_xlim()
ax.xaxis.set_ticks(np.arange(start, end, TICK))
ax.figure.canvas.draw()
line1.set_data(x[0:frame+1], y[0:frame+1])
return (line1,)
# main
NUM = 100
TICK = 1
PAN = 10
x = np.arange(start=1, stop=NUM + 1, step=1)
for i in range(NUM):
y = np.random.rand(NUM) * 100
fig, ax = plt.subplots()
ax.set_xlim(0, PAN)
start, end = ax.get_xlim()
ax.xaxis.set_ticks(np.arange(start, end, TICK))
ax.set_ylim(0, 100)
line1, = ax.plot([], [], color="r")
ani = animation.FuncAnimation(fig, update, frames=len(x), interval=1000, repeat=False)
plt.show()
I have tried many ways to merge them together, but I just cant seem to find the correct way to go about it.
Thanks in advance!!
Showing the last N time points is quite easy. Just use DataFrame.tail() to get the last N rows of your dataframe.
Note that when doing an animation, the recommended way is to create your axes and artists outside the animation code, and only update your artists' data inside the animate code.
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
l1, = ax.plot([], [], marker='o', label='Room 1 Temp')
l2, = ax.plot([], [], marker='o', label='Room 2 Temp')
l3, = ax.plot([], [], marker='o', label='Room 3 Temp')
plt.xlabel("Time")
plt.ylabel("Temperature °C")
plt.title("Live temperature of Rooms")
plt.legend(loc='upper left')
plt.tight_layout()
def animate(i, N):
data = pd.read_csv('x.csv').tail(N)
l1.set_data(data['Time'], data['R1Temp'])
l2.set_data(data['Time'], data['R2Temp'])
l3.set_data(data['Time'], data['R3Temp'])
ax.relim()
ax.autoscale_view()
return l1, l2, l3
ani = FuncAnimation(fig, animate, interval=1000, frames=None, fargs=(10,))
plt.show()

Matplotlib y axis is not ordered

I'm getting data from serial port and draw it with matplotlib. But there is a problem. It is that i cannot order y axis values.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from deneme_serial import serial_reader
collect = serial_reader()
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
xs=[]
ys=[]
def animate(i, xs, ys):
xs = collect.collector()[0]
ys = collect.collector()[1]
ax.clear()
ax.plot(xs)
ax.plot(ys)
axes=plt.gca()
plt.xticks(rotation=45, ha='right')
plt.subplots_adjust(bottom=0.30)
plt.title('TMP102 Temperature over Time')
plt.ylabel('Temperature (deg C)')
ani = animation.FuncAnimation(fig, animate, fargs=(xs,ys), interval=1000)
plt.show()
Below graph is result of above code
This happened to me following the same tutorial.
My issue was the variables coming from my instrument were strings. Therefore, there is no order. I changed my variables to float and that fixed the problem
xs.append(float(FROM_INSTRUMENT))

Showing subplots at each pass of a loop

I would essentially like to do the following:
import matplotlib.pyplot as plt
import numpy as np
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
for i in range(10):
ax1.scatter(i, np.sqrt(i))
ax1.show() # something equivalent to this
ax2.scatter(i, i**2)
That is, each time a point is plotted on ax1, it is shown - ax2 being shown once.
You cannot show an axes alone. An axes is always part of a figure. For animations you would want to use an interactive backend. Then the code in a jupyter notebook could look like
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig1, ax1 = plt.subplots()
fig2, ax2 = plt.subplots()
frames = 10
x = np.arange(frames)
line1, = ax1.plot([],[], ls="", marker="o")
line2, = ax2.plot(x, x**2, ls="", marker="o")
ax2.set_visible(False)
def animate(i):
line1.set_data(x[:i], np.sqrt(x[:i]))
ax1.set_title(f"{i}")
ax1.relim()
ax1.autoscale_view()
if i==frames-1:
ax2.set_visible(True)
fig2.canvas.draw_idle()
ani = FuncAnimation(fig1, animate, frames=frames, repeat=False)
plt.show()
If you want to change plots dynamically I'd suggest you don't redraw the whole plot every time, this will result in very laggy behavior. Instead you could use Blit to do this. I used it in a previous project. Maybe it can help you too if you just take the parts from this you need:
Python project dynamically updating plot

clear ax2 in matplotlib

I'm trying to plot two lines with different scales with matplotlib.
It is currently working, except when I run my code the second Y axis messes up while updating.
Here is the code I'm using:
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.pyplot import cm
from datetime import datetime
import numpy as np
import matplotlib.animation as animation
def animate(i, fig, ax):
# Converter function
datefunc = lambda x: mdates.date2num(datetime.strptime(x, '%d-%m-%Y_%H:%M:%S'))
# Read data from 'file.dat'
dates, levels, temp = np.genfromtxt('datosPlot.txt', # Data to be read
converters={0: datefunc}, # Formatting of column 0
dtype=float, # All values are floats
usecols=(0,1,2), #Leer las tres primeras columnas de datos.txt
unpack=True) # Unpack to several variables
# Configure x-ticks
ax1.clear()
ax1.set_xticks(dates) # Tickmark + label at every plotted point
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%d/%m/%Y %H:%M'))
ax2 = ax1.twinx()
fig.tight_layout()
fig = plt.figure()
ax1 = fig.add_subplot(111)
ani = animation.FuncAnimation(fig, animate, fargs=(fig, ax1), interval=1000)
plt.show()
My data (datosPlot.txt) look like this:
14-01-2017_14:01:16 1 16
14-01-2017_14:01:19 14 22
14-01-2017_14:01:22 2 17
14-01-2017_14:01:25 4 19
14-01-2017_14:01:28 6 24
14-01-2017_14:01:31 12 19
14-01-2017_14:01:34 4 18
14-01-2017_14:01:37 9 20
First column is the X axis (date_time), second column is pH, third column is Temperature.
I've tried adding a ax2.clear() before and after calling ax2 = ax1.twinx(), but it doesn't work. How can I clear it, as I'm able with ax1?
Here is what it looks like when I don't add any ax2.clear():
Try to create the axes outside of your animation and only use as little code as you really need in each animation step.
The following is a runnable example, where you would need to replace the read in function em.genfromtxt() with your original call to np.genfromtxt(....).
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
import numpy as np
import matplotlib.animation as animation
##### Emulator to generate data #########
class emulator():
def __init__(self):
self.dates = []
self.levels = []
self.temp = []
def genfromtxt(self):
self.dates.append(mdates.date2num(datetime.now()))
self.levels.append(np.random.randint(1,14))
self.temp.append(np.random.rand(1)*16+4)
return self.dates, self.levels, self.temp
em = emulator()
##### End of Emulator to generate data #########
# Converter function
datefunc = lambda x: mdates.date2num(datetime.strptime(x, '%d-%m-%Y_%H:%M:%S'))
def animate(i):
# Read data from 'file.dat'
# instead we use an emulator here, replace with your original genfromtxt function
dates, levels, temp = em.genfromtxt()
# Configure x-ticks
ax1.clear()
ax2.clear()
ax1.grid(True)
ax2.grid(True)
ax1.plot_date(dates, levels, ls='-', marker='.', color='red', label='pH')
ax2.plot_date(dates, temp, ls='-', marker='.', color='blue', label='Temperatura C')
ax1.set_xticks(dates) # Tickmark + label at every plotted point
ax1.locator_params(axis='x',nbins=10)
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%d/%m/%Y %H:%M'))
#Leyendas
lines = ax1.get_lines() + ax2.get_lines()
plt.legend(lines, [l.get_label() for l in lines], loc=2)
fig.autofmt_xdate(rotation=45)
fig.tight_layout()
fig = plt.figure()
# we create both axes outside the animation and already set those parameters
# which stay the same throughout the animation.
ax1 = fig.add_subplot(111)
ax1.set_title('pH y Temp')
ax1.set_ylabel('pH')
ax2 = ax1.twinx() # This should happen outside the animation already.
ax2.set_ylabel('Temperatura C')
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()

Categories