Matplotlib animation, why no legends? - python

I have the following matplotlib animation. The lines get plotted with the correct color but for some reason no legend is shown when showing the plot. Notice tat plt.legend() is called. A curious thing is that the legends get shown when it is saved to a movie
import math
import argparse
import os
import json
import sys
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation ,FFMpegWriter
line_x=[ 0,1,2,3,4,5,6,7,8,9,10,11,12 ]
line1_y=[ 3,5,7,9,11,19,23,26,29,31,37,40,45 ]
line2_y=[0,2,5,7,10,10,8,5,3,2,1,3,5]
line3_y=[39,38,32,29,26,22,19,13,10,8,7,6,3]
set_lines=[line1_y,line2_y,line3_y]
n_lineas=[1,2,3,1,3,2,3,1,3,2,1,2]
show=True
thecolors=['blue','red','violet']
thelegends=['unus','duo','tres']
print(sys.argv)
if len(sys.argv)==2 and sys.argv[1]=='movie':
show=False
def get_n(thelist,c):
while(c>=len(thelist)):
c-len(thelist)
return thelist[c]
class Update:
def __init__(self,ax,limit_x):
self.ax = ax
self.lx=limit_x
if limit_x!=0:
self.ax.set_xlim(0,limit_x)
self.ax.set_ylim(0,45)
self.ax.set_aspect('equal')
self.ax.grid(True)
self.lines=()
self.counter=0
def __call__(self, frame):
print("Frame: ",frame)
lines=[]
self.ax.cla()
self.ax.set_xlim(0,self.lx)
self.ax.set_ylim(0,45)
n_lines_this_time=get_n(n_lineas,self.counter)
self.counter+=1
print(n_lines_this_time,"lines this time")
for myline in range(n_lines_this_time):
#line,=self.ax.plot([],[],'.-',color=gt_color,label=legend)
line,=self.ax.plot([],[],'.-',color=thecolors[myline],label=thelegends[myline])
x = []
y = []
for v in range(13):
x.append(line_x[v])
y.append(set_lines[myline][v])
line.set_xdata(x)
line.set_ydata(y)
lines.append(line)
plt.legend()
self.lines=tuple(lines)
return self.lines
def init(self):
print("Init")
line,=self.ax.plot([],[])
return line,
fig, ax = plt.subplots(1, 1,figsize=(10,10))
plt.gcf().canvas.mpl_connect(
'key_release_event',
lambda event: [exit(0) if event.key == 'escape' else None])
plt.xlabel("Y (meters)")
plt.ylabel("X (meters)")
ug_i = Update(ax,13)
anim = FuncAnimation(fig, ug_i,init_func=ug_i.init, frames=10, interval=1000, blit=True,repeat=False)
if not show: #This is not working. It does not erase the plots
writervideo = FFMpegWriter(fps=1)
anim.save('whatever.mp4', writer=writervideo)
print('done')
plt.close()
else:
#plt.legend()
plt.show()
Another thing I noticed is:
-In the animation there is a grid and X,Y labels but not legends but
-In the movie there is no grid and no X,Y labels but there are legends

Related

matplotlib animation stock one frame over the other

I have the following minimum reproducible example:
import math
import argparse
import os
import json
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation ,FFMpegWriter
line_x=[ 0,1,2,3,4,5,6,7,8,9,10,11,12 ]
line1_y=[ 3,5,7,9,11,19,23,26,29,31,37,40,45 ]
line2_y=[0,2,5,7,10,10,8,5,3,2,1,3,5]
line3_y=[39,38,32,29,26,22,19,13,10,8,7,6,3]
set_lines=[line1_y,line2_y,line3_y]
n_lineas=[1,2,3,1,3,2,3,1,3,2,1,2]
show=True #Here chabnge to False
def get_n(thelist,c):
while(c>=len(thelist)):
c-len(thelist)
return thelist[c]
class Update:
def __init__(self,ax,limit_x):
self.ax = ax
if limit_x!=0:
self.ax.set_xlim(0,limit_x)
self.ax.set_ylim(45)
self.ax.set_aspect('equal')
self.ax.grid(True)
self.lines=()
self.counter=0
def __call__(self, frame):
print("Frame: ",frame)
lines=[]
n_lines_this_time=get_n(n_lineas,self.counter)
self.counter+=1
print(n_lines_this_time,"lines this time")
for myline in range(n_lines_this_time):
#line,=self.ax.plot([],[],'.-',color=gt_color,label=legend)
line,=self.ax.plot([],[],'.-')
x = []
y = []
for v in range(13):
x.append(line_x[v])
y.append(set_lines[myline][v])
line.set_xdata(x)
line.set_ydata(y)
lines.append(line)
self.lines=tuple(lines)
return self.lines
def init(self):
print("Init")
line,=self.ax.plot([],[])
return line,
fig, ax = plt.subplots(1, 1,figsize=(10,10))
plt.gcf().canvas.mpl_connect(
'key_release_event',
lambda event: [exit(0) if event.key == 'escape' else None])
plt.xlabel("Y (meters)")
plt.ylabel("X (meters)")
ug_i = Update(ax,13)
anim = FuncAnimation(fig, ug_i,init_func=ug_i.init, frames=10, interval=1000, blit=True,repeat=False)
if not show: #This is not working. It does not erase the plots
writervideo = FFMpegWriter(fps=60)
anim.save('whatever.mp4', writer=writervideo)
print('done')
plt.close()
else:
plt.legend()
plt.show()
My problem is when show is True, I can see my animation. (it shows a different number of line each frame)
but when show is False, it seems the movie has everything all together because all the lines get shown without change.

MatPlotlib plotting live graph from sensor

I'm writing a GUI and part of that requires a live graph be displayed. At the moment i'm just getting a straight line being plot.
from mcculw import ul
from mcculw.enums import ULRange
from datetime import datetime as dt
from mcculw.ul import ULError
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import tkinter as Tk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
board_num = 0
channel = 0
ai_range = ULRange.BIP5VOLTS
try:
# Get a value from the device
value = ul.a_in(board_num, channel, ai_range)
# Convert the raw value to engineering units
eng_units_value = ul.to_eng_units(board_num, ai_range, value)
# Display the raw value
print("Raw Value: " + str(value))
# Display the engineering value
print("Engineering Value: " + '{:.3f}'.format(eng_units_value))
except ULError as e:
# Display the error
print("A UL error occurred. Code: " + str(e.errorcode)
+ " Message: " + e.message)
x=[]
y=[]
fig, ax = plt.subplots()
def animate(i, x, y):
x.append(i)
y.append(eng_units_value)
ax.clear()
ax.plot(x, y)
x = x[-10:]
y = y[-10:]
ani = animation.FuncAnimation(fig, animate, fargs=(x, y), interval=100)
plt.show()
This is a snippet of the problem with the code I have.
Graph I get
No error codes - just a problem with getting a new value every time it plots i think.

Live scatter plot fetching data from firebase every 2 second

I have a mobile application which does some calculation and throws x,y coordinates and are updated on firebase every 2 seconds.
Next i want those coordinates to be plotted on a floor plan live. For that i am using Scatter plot over the floor plan image. But i cannot make it live as soon as the data is fetched need help with that.
Here is the code till now:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
xs = []
ys = []
fig = plt.figure()
scat = plt.scatter(xs, ys, c='r', s=100)
def main():
graph_data = open("testfile.txt","r").read()
lines = graph_data.split("\n")
for line in lines:
if len(line)>1:
x,y = line.split(",")
xs.append(x)
ys.append(y)
plt.scatter(xs,ys)
print(xs)
print(ys)
ani = animation.FuncAnimation(fig,main(),fargs=(scat))
plt.show()
Getting error with animation.FuncAnimation TypeError: NoneType object argument after * must be an iterable, not PathCollection
You can fetch data in a separate thread while updating the plot in the main one. Here is a complete working example:
#!/usr/bin/env python3
import time
from queue import Queue
from threading import Thread, Event
import numpy as np
import matplotlib.pyplot as plt
FETCH_DELAY = 2
def fetch_data(queue, stop):
while not stop.is_set():
x, y = np.random.randn(2)
queue.put((x, y))
time.sleep(FETCH_DELAY)
def limits(array, offset=1):
return array.min() - offset, array.max() + offset
def main():
stop = Event()
queue = Queue()
worker = Thread(target=fetch_data, args=(queue, stop))
worker.start()
plt.ion()
fig, ax = plt.subplots()
plot = ax.scatter([], [])
try:
while True:
data = queue.get()
data = np.array(data)
plt_data = plot.get_offsets()
plt_data = np.vstack((plt_data, data))
plot.set_offsets(plt_data)
fig.canvas.draw()
xmin, xmax = limits(plt_data[:, 0])
ymin, ymax = limits(plt_data[:, 1])
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
queue.task_done()
except KeyboardInterrupt:
pass
finally:
stop.set()
worker.join()
if __name__ == '__main__':
main()
Save it as plot_update.py file and run it from the command line:
python3 plot_update.py
Here is the solution without using threads it becomes very simple:
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
from firebase import firebase
firebase = firebase.FirebaseApplication('Firebase url', None)
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y,c=np.random.rand(3,))
plt.xlim(12,13)
plt.ylim(77,78)
def animate(i):
xs = firebase.get('/Lat',None)
ys = firebase.get('/Long',None)
xs = round(xs,2)
ys = round(ys,2)
file = open("testfile.txt","a+")
file.write("{0},{1} \n".format(xs,ys))
file.close()
graph_data = open("testfile.txt","r").read()
lines = graph_data.split("\n")
for line in lines:
if len(line)>1:
xs,ys = line.split(",")
x.append(xs)
y.append(ys)
sc.set_offsets(np.c_[x,y])
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=2, interval=500, repeat=True)
plt.show()

How do I animate a scatterplot over a basemap in matplotlib?

The code below generates a animated basemap, but not exactly the one I want: I want the scatterplot from the previous frame to disappear, but it persists through the remainder of the animation.
I suspect it has something to do with my not understanding what the basemap really is. I understand calling it on lat/lons to project them to x/y, but I don't entirely get what's going on when I call event_map.scatter().
import random
import os
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib import animation
import pandas as pd
from IPython.display import HTML
# Enables animation display directly in IPython
#(http://jakevdp.github.io/blog/2013/05/12/embedding-matplotlib-animations/)
from tempfile import NamedTemporaryFile
VIDEO_TAG = """<video controls>
<source src="data:video/x-m4v;base64,{0}" type="video/mp4">
Your browser does not support the video tag.
</video>"""
def anim_to_html(anim):
if not hasattr(anim, '_encoded_video'):
with NamedTemporaryFile(suffix='.mp4') as f:
anim.save(f.name, fps=20, extra_args=['-vcodec', 'libx264'])
video = open(f.name, "rb").read()
anim._encoded_video = video.encode("base64")
return VIDEO_TAG.format(anim._encoded_video)
def display_animation(anim):
plt.close(anim._fig)
return HTML(anim_to_html(anim))
animation.Animation._repr_html_ = anim_to_html
FRAMES = 20
POINTS_PER_FRAME = 30
LAT_MIN = 40.5
LAT_MAX = 40.95
LON_MIN = -74.15
LON_MAX = -73.85
FIGSIZE = (10,10)
MAP_BACKGROUND = '.95'
MARKERSIZE = 20
#Make Sample Data
data_frames = {}
for i in range(FRAMES):
lats = [random.uniform(LAT_MIN, LAT_MAX) for x in range(POINTS_PER_FRAME)]
lons = [random.uniform(LON_MIN, LON_MAX) for x in range(POINTS_PER_FRAME)]
data_frames[i] = pd.DataFrame({'lat':lats, 'lon':lons})
class AnimatedMap(object):
""" An animated scatter plot over a basemap"""
def __init__(self, data_frames):
self.dfs = data_frames
self.fig = plt.figure(figsize=FIGSIZE)
self.event_map = Basemap(projection='merc',
resolution='i', area_thresh=1.0, # Medium resolution
lat_0 = (LAT_MIN + LAT_MAX)/2, lon_0=(LON_MIN + LON_MAX)/2, # Map center
llcrnrlon=LON_MIN, llcrnrlat=LAT_MIN, # Lower left corner
urcrnrlon=LON_MAX, urcrnrlat=LAT_MAX) # Upper right corner
self.ani = animation.FuncAnimation(self.fig, self.update, frames=FRAMES, interval=1000,
init_func=self.setup_plot, blit=True,
repeat=False)
def setup_plot(self):
self.event_map.drawcoastlines()
self.event_map.drawcounties()
self.event_map.fillcontinents(color=MAP_BACKGROUND) # Light gray
self.event_map.drawmapboundary()
self.scat = self.event_map.scatter(x = [], y=[], s=MARKERSIZE,marker='o', zorder=10)
return self.scat
def project_lat_lons(self, i):
df = data_frames[i]
x, y = self.event_map(df.lon.values, df.lat.values)
x_y = pd.DataFrame({'x': x, 'y': y}, index=df.index)
df = df.join(x_y)
return df
def update(self, i):
"""Update the scatter plot."""
df = self.project_lat_lons(i)
self.scat = self.event_map.scatter(x = df.x.values, y=df.y.values, marker='o', zorder=10)
return self.scat,
s = AnimatedMap(data_frames)
s.ani
It looks like you're simply adding a new scatter plot at each update. What you should do instead is change the data in the existing path collection at each update. Try something along the lines of
def update(self, i):
"""Update the scatter plot."""
df = self.project_lat_lons(i)
new_offsets = np.vstack([df.x.values, df.y.values]).T
self.scat.set_offsets(new_offsets)
return self.scat,
Note that I haven't tested this.

Python Script to animate a set of lines in a class

I am trying to put the matplotlib.animation set into a class function. Though I don't seem to be having much luck. I have tried both, FunctionAnimation() & ArtistAnimation(). For both I don't seem to be able to get them to work (though they are vastly different).
# ------------------------------ #
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# ------------------------------ #
class AniPlot():
def __init__(self):
self.fig = plt.figure()
self.ax = plt.axes(xlim=(-3.5, 3.5), ylim=(-5, 2))
self.line, = self.ax.plot([], [], lw=2)
def set_data(self,tvector):
self.data = tvector
def ani_init(self):
self.line.set_data([], [])
def ani_update(i):
x = self.data[i][0]
y = self.data[i][1]
self.line.set_data(x, y)
return self.line,
def animate(self):
anim = animation.FuncAnimation(self.fig, self.ani_update, init_func=self.ani_init,
frames=4, interval=20, blit=True)
plt.show()
# ------------------------------ #
data = [
[[0,0,1,0],[0,-1,-2,-3]],
[[0,0,0,0.1],[0,-1,-3,-4]],
[[0,0,0.5,0],[0,-1,-2.5,-3.5]],
[[0,0,1,2],[0,-1,-2,-2.5]]
]
myani = AniPlot()
myani.set_data(data)
myani.animate()
I want to try get my head around it, rather than use someone else's code. Though I did use others as a starting point. Can anyone help?
(warning: Newbie here.)
I think the best way for "anim" to stick is actually to set it as instance variable, using self.anim:
self.anim = ...
You also need to add "self" here:
def ani_update(self, i)
I use Spyder 2.1.10 and it seems to be working, although the animation is a bit fast.
You can either set blit as False, or as True but make sure you replace the line return self.line by return self.line,.
#!/usr/bin/env python3
# ------------------------------ #
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# ------------------------------ #
class AniPlot():
def __init__(self):
self.fig = plt.figure()
self.ax = plt.axes(xlim=(-3.5, 3.5), ylim=(-5, 2))
self.line, = self.ax.plot([], [], lw=2)
def set_data(self,data):
self.data = data
def ani_init(self):
self.line.set_data([], [])
return self.line
def ani_update(self, i):
x = self.data[i][0]
y = self.data[i][1]
self.line.set_data(x, y)
return self.line
def animate(self):
self.anim = animation.FuncAnimation(self.fig, self.ani_update, init_func=self.ani_init, frames=4, interval=20, blit=False)
plt.show()
# ------------------------------ #
data = [
[[0,0,1,0],[0,-1,-2,-3]],
[[0,0,0,0.1],[0,-1,-3,-4]],
[[0,0,0.5,0],[0,-1,-2.5,-3.5]],
[[0,0,1,2],[0,-1,-2,-2.5]]
]
myani = AniPlot()
myani.set_data(data)
myani.animate()

Categories

Resources