scatter update with animation - python

I am trying to do a real time scatter-kind plot using matplotlib's animation module but I'm quite a newbie with it. My objective is to update the plot whenever I receive the data I want to plot, so that any time data is received, previous points disappear and the new ones are plotted.
My program can be written like this if I substitute the data receiving with a endless loop and a random generation of data:
fig = plt.figure()
skyplot = fig.add_subplot(111, projection='polar')
skyplot.set_ylim(90) # sets radius of the circle to maximum elevation
skyplot.set_theta_zero_location("N") # sets 0(deg) to North
skyplot.set_theta_direction(-1) # sets plot clockwise
skyplot.set_yticks(range(0, 90, 30)) # sets 3 concentric circles
skyplot.set_yticklabels(map(str, range(90, 0, -30))) # reverse labels
plt.ion()
while(1):
azimuths = random.sample(range(360), 8)
elevations = random.sample(range(90), 8)
colors = numpy.random.rand(3,1)
sat_plot = satellite()
ani= animation.FuncAnimation(fig, sat_plot.update, azimuths, elevations, colors)
class satellite:
def __init__(self):
self.azimuths = []
self.elevations = []
self.colors = []
self.scatter = plt.scatter(self.azimuths, self.elevations, self.colors)
def update(self, azimuth, elevation, colors):
self.azimuths = azimuth
self.elevations = elevation
return self.scatter
Right now, I'm getting the following error:
> Traceback (most recent call last):
File "./skyplot.py", line 138, in <module>
ani= animation.FuncAnimation(fig, sat_plot.update, azimuths, elevations, colors)
File "/usr/lib/pymodules/python2.7/matplotlib/animation.py", line 442, in __init__
TimedAnimation.__init__(self, fig, **kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/animation.py", line 304, in __init__
Animation.__init__(self, fig, event_source=event_source, *args, **kwargs)
File "/usr/lib/pymodules/python2.7/matplotlib/animation.py", line 53, in __init__
self._init_draw()
File "/usr/lib/pymodules/python2.7/matplotlib/animation.py", line 469, in _init_draw
self._drawn_artists = self._init_func()
TypeError: 'list' object is not callable
Can anyone tell me what I'm doing wrong and how could I do this?
Thanks in advance

I think you do not need an animation. You need a simple endless loop (while for example) with plot update in a thread. I can propose something like this:
import threading,time
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
data = np.random.uniform(0, 1, (5, 3))
plt.scatter(data[:, 0], data[:,1],data[:, 2]*50)
def getDataAndUpdate():
while True:
"""update data and redraw function"""
new_data = np.random.uniform(0, 1, (5, 3))
time.sleep(1)
plt.clf()
plt.scatter(new_data[:, 0], new_data[:, 1], new_data[:, 2] * 50)
plt.draw()
t = threading.Thread(target=getDataAndUpdate)
t.start()
plt.show()
The result is an animated-like figure with scatterplot.

Related

RuntimeError: Can not put single artist in more than one figure when using matplotlib 1.5

Here is my code:
import matplotlib.pyplot as plt
plt.figure(1) # the first figure
plt.subplot(211) # the first subplot in the first figure
plt.plot([1, 2, 3])
plt.subplot(212) # the second subplot in the first figure
plt.plot([4, 5, 6])
plt.figure(2) # a second figure
plt.plot([4, 5, 6]) # creates a subplot(111) by default
plt.text(.5,1.5,'211',figure = 211) #tring to add text in previous subplot
plt.figure(1) # figure 1 current; subplot(212) still current
plt.subplot(211) # make subplot(211) in figure1 current
plt.title('Easy as 1, 2, 3') # subplot 211 title
The error:
Traceback (most recent call last):
File "C:/Users/ezhou/Desktop/python/test3.py", line 11, in <module>
plt.text(.5,1.5,'211',figure = 211)
File "C:\Python27\lib\site-packages\matplotlib\pyplot.py", line 3567, in text
ret = gca().text(x, y, s, fontdict=fontdict, withdash=withdash, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\axes\_axes.py", line 619, in text
self._add_text(t)
File "C:\Python27\lib\site-packages\matplotlib\axes\_base.py", line 1720, in _add_text
self._set_artist_props(txt)
File "C:\Python27\lib\site-packages\matplotlib\axes\_base.py", line 861, in _set_artist_props
a.set_figure(self.figure)
File "C:\Python27\lib\site-packages\matplotlib\artist.py", line 640, in set_figure
raise RuntimeError("Can not put single artist in "
RuntimeError: Can not put single artist in more than one figure
I was trying to understand the kwargs 'figure' in class matplotlib.text.Text(), but it will always reply 'Can not put single artist in more than one figure'. So I was confused about how to use this 'figure' kwarg. Can anyone give me some advise? Thanks!
You shouldn't pass figure as a kwarg, instead use text method of a Figure (or Axes) instance. Example:
import matplotlib.pyplot as plt
fig1, fig2 = plt.figure(1), plt.figure(2)
sp1, sp2 = fig1.add_subplot(211), fig2.add_subplot(211)
sp1.plot([1, 2, 3])
sp2.plot([0, 1, 3])
fig1.text(.5, .3, 'whole figure')
sp2.text(.5, .5, 'subplot')
Please note that coordinates are relative (0, 1).
P.S if you find matplotlib needlessly complicated (as I do), you may wish to have a look at Plotly

matplotlib: fail to plot line or bar; valid axes

I'm attempting generate matplotlib images through a loop. I have two iterations of loops that generate images. The first loop works, the second doesn't. The axes are valid, I can see that when I print the numpy arrays.
plt_mean = float(week_occurrences) / len(x_axis)
y_np = np.array(y_axis)
std_d = np.std(y_np)
plt.plot(x_axis, y_np, color='#758AA8')
plt.axis([y_axis[0], y_axis[6], 0, int(y_max * 1.2)])
plt.axhline(plt_mean, color='black')
plt.ylabel("Events")
plt.xlabel("Day")
plt.title(event)
plt.savefig("tmp/{} {}.jpg".format(event, y_axis[0]), bbox_inches='tight')
plt.clf()
print(event)
print(y_max)
print(plt_mean)
print(x_axis)
raw_input(y_np)
output:
A user account was changed.
384
111.571428571
[5, 22, 4, 384, 363, 3, 0]
[166 167 168 169 170 171 172]
What am I missing? Why won't it plot the associated lines?
I believe the line is plotted, but I think your axis limits are wrong. I'm not entirely sure what you're trying to do, because it looks like you've inverted your x and y.
here is the result after the line:
plt.plot(x_axis, y_np, color='#758AA8')
However, after the line
plt.axis([y_axis[0], y_axis[6], 0, int(y_max * 1.2)])
the axes limit do not make any sense anymore and you're seeing a region where there are no data.
plt.axis() takes its argument in the order [xmin, xmax, ymin, ymax]
Looks like you didn't define y_max correctly. This works for me:
import numpy as np
import matplotlib.pylab as plt
x_axis = [5, 22, 4, 384, 363, 3, 0]
y_axis = [166, 167, 168, 169, 170, 171, 172]
y_max = np.max(y_axis)
event = np.str('A user account was changed.')
week_occurrences = 780.999999997
plt_mean = float(week_occurrences) / len(x_axis)
y_np = np.array(y_axis)
std_d = np.std(y_np)
plt.plot(x_axis, y_np, color='#758AA8')
plt.axis([y_axis[0], y_axis[6], 0, int(y_max * 1.2)])
plt.axhline(plt_mean, color='black')
plt.ylabel("Events")
plt.xlabel("Day")
plt.title(event)
# plt.savefig("tmp/{} {}.jpg".format(event, y_axis[0]), bbox_inches='tight')
# plt.clf()
print(event)
print(y_max)
print(plt_mean)
print(x_axis)

Matplotlib Animation: Animating Circles position from scipy array

I am trying to animate position of Circles in matplotlib and don't think have got it right
my data is 2D matrix size ((1000,4)) each row contains the y location of 4 circles, x is always 1
import numpy as np
from matplotlib import pyplot
from matplotlib import animation
data = np.zeros((1000,4))
fig=pyplot.figure()
ax=pyplot.axes([0,40,0,40])
circle1=pyplot.Circle((data[0,0],1),0.2,fc='y')
circle2=pyplot.Circle((data[0,1],1),0.2,fc='g')
circle3=pyplot.Circle((data[0,2],1),0.2,fc='r')
circle4=pyplot.Circle((data[0,3],1),0.2,fc='b')
def init():
circle1.center=(data[0,0],1)
circle2.center=(data[0,1],1)
circle3.center=(data[0,2],1)
circle4.center=(data[0,3],1)
ax.add_patch(circle1)
ax.add_patch(circle2)
ax.add_patch(circle3)
ax.add_patch(circle4)
return circle1, circle2, circle3, circle4
def animate(i):
for state in data:
circle1.center=(state[0],1)
circle2.center=(state[1],1)
circle3.center=(state[2],1)
circle4.center=(state[3],1)
return circle1, circle2, circle3, circle4
anim=animation.FuncAnimation(fig,animate,init_func=init,frames=1000,blit=True)
pyplot.show()
The following error is thrown:
File "C:\Python27\Lib\site-packages\matplotlib\transforms.py", line 2242, in inverted
return CompositeGenericTransform(self._b.inverted(), self._a.inverted() )
File "C:\Python27\Lib\site-packages\matplotlib\transforms.py", line 1680, in inverted
self._inverted = Affine2D(inv(mtx), shorthand_name=shorthand_name)
File "C:\Python27\Lib\site-packages\numpy\linalg\linalg.py", line 520, in inv
ainv = _umath_linalg.inv(a, signature=signature, extobj=extobj)
File "C:\Python27\Lib\site-packages\numpy\linalg\linalg.py", line 90, in _raise_linalgerror_singular
raise LinAlgError("Singular matrix")
LinAlgError: Singular matrix
I fixed a few parts in your code.
I put some values in data so that circles animate.
You probably wanted to put the center of circles in data at each time step, then for loop in animate is not necessary.
It seems that animation function does not work with patches on Mac if you don't use Qt4Agg backend. If you use Mac, you probably need to add the first two lines below.
import matplotlib
matplotlib.use('Qt4Agg')
import numpy as np
from matplotlib import pyplot
from matplotlib import animation
from math import sin
data = np.zeros((1000,4))
data[:,0] = [20*(1+sin(float(x)/200)) for x in range(1000)]
data[:,1] = [20*(1+sin(float(x)/100)) for x in range(1000)]
data[:,2] = [20*(1+sin(float(x)/50)) for x in range(1000)]
data[:,3] = [20*(1+sin(float(x)/25)) for x in range(1000)]
fig=pyplot.figure()
ax = pyplot.axes(xlim=(0, 40), ylim=(0, 40))
circle1=pyplot.Circle((data[0,0],1.0),0.2,fc='y')
circle2=pyplot.Circle((data[0,1],1.0),0.2,fc='g')
circle3=pyplot.Circle((data[0,2],1.0),0.2,fc='r')
circle4=pyplot.Circle((data[0,3],1.0),0.2,fc='b')
def init():
circle1.center=(data[0,0],1)
circle2.center=(data[0,1],1)
circle3.center=(data[0,2],1)
circle4.center=(data[0,3],1)
ax.add_patch(circle1)
ax.add_patch(circle2)
ax.add_patch(circle3)
ax.add_patch(circle4)
return circle1, circle2, circle3, circle4
def animate(i):
# for state in data:
circle1.center=(data[i,0],1)
circle2.center=(data[i,1],1)
circle3.center=(data[i,2],1)
circle4.center=(data[i,3],1)
return circle1, circle2, circle3, circle4
anim=animation.FuncAnimation(fig,animate,init_func=init,frames=1000,blit=True)
pyplot.show()

Animate multiple points on Matplotlib Basemap over time

I am trying to create an animated plot of a series of points with lat/lon positions on a matplotlib.basemap map. Each point has a series of positions for a series of days, which I have read into a pandas DataFrame.
I've tried to modify the procedure used HERE to do this, but I am getting an error that global name 'points' is not defined. I've tried to declare this as a global within the init routine, but that didn't help.
How might I do this?
Example data:
day,id, lon, lat
156, 1, 67.53453, -4.00454
156, 2, 66.73453, 0.78454
156, 3, 68.23453, -1.01454
157, 1, 67.81453, -4.26454
157, 2, 66.42653, 0.91454
157, 3, 69.11253, -1.01454
158, 1, 68.12453, -3.26454
158, 2, 67.10053, 1.01454
158, 3, 68.01253, -2.61454
Calling routine:
if datafile != None:
data = readdata(datafile)
dates = np.unique(data.daynr).values
x,y = m(0,0)
point = m.plot(x,y, 'ro', markersize=5)[0]
points = list()
anim = animation.FuncAnimation(plt.gcf(), animate,
init_func=init, frames=20,
interval=500, blit=True)
# Add current date/time or something to make unique
anim.save('movement.mp4', fps=15,
extra_args=['-vcodec', 'libx264'])
init, animate, and data reading routines:
def init():
for pt in points:
pt.set_data([], [])
return points
def animate(i):
lons = data.lons[data.daynr==dates[i]]
lats = data.lats[data.daynr==dates[i]]
i = 0
for lon,lat, pt in zip(points, lons, lats):
x, y = map(lon,lat)
pt.set_data(x, y)
i = i + 1
return points
def readdata(datafile):
dtypes = np.dtype([
('daynr',int), #00 - Simulation day number
('id',int), #01 - Id
('lon',float), #02 - Longitude
('lat',float), #03 - Latitude
])
f = open(datafile, 'rb')
data = pd.read_csv(f, index_col=False, names=dtypes.names,
dtype=dtypes, header=None)
f.close()
return data
So... my first problem was that I hadn't realized that variables within a function in python were not considered 'global' the functions that are called within it.
To get around this, I made my init() and animate(i) functions 'subfunctions', which then allowed variables declared in the parent function to be treated as global by the init() and animate(i) sub functions (see code below).
I found this blog article very helpful to arrive at my solution.
As was this SO question.
NOTE: I've edited my code a bit for the purpose of this answer, so please comment if this doesn't work properly for you.
My plotting function and the calling routine:
import pandas as pd
import numpy as np
import pyproj
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
def makeplot(plot_data=False):
''' plot function with optional data animation
if data is supplied (as a `pandas` DataFrame), subfuntions `init()`
and `animate(i)` will animate recurring, multiple position values
per unique day and save to file.'''
def init():
# initialize each plot point created in the list `points`
for pt in points:
pt.set_data([], [])
return points
def animate(i):
#Only routine if `i` doesn't exceed number of unique days to animate
if i < len(data_dates):
print 'Animation frame:', i, '; Simulation Day:', data_dates[i]
lons = data.lons[data.daynr==dates[i]].values
lats = data.lats[data.daynr==dates[i]].values
j = 0
for pt,lon,lat in zip(points, lons, lats):
x, y = m(lon,lat)
pt.set_data(x, y)
j = j + 1
return points
# Define ellipsoid object for distance measurements
g = pyproj.Geod(ellps='WGS84') # Use WGS84 ellipsoid
r_equator = g.a # earth's radius at equator
r_poles = g.b # earth's radius through poles
lon0, lat0, map_width, map_height = center_map(poly_lons, poly_lats, 1.1)
m = Basemap(width=map_width,height=map_height,
rsphere=(r_equator, r_poles),\
resolution='f', projection='laea',\
lat_ts=lat0,\
lat_0=lat0,lon_0=lon0)
# Draw parallels and meridians.
m.drawparallels(np.arange(-80.,81.,5.), labels=[1,0,0,0], fontsize=10)
m.drawmeridians(np.arange(-180.,181.,10.), labels=[0,0,0,1], fontsize=10)
m.drawmapboundary(fill_color='white')
m.drawcoastlines(linewidth=0.2)
m.fillcontinents(color='gray', lake_color='white') #aqua
# Animate if position data is supplied with plotting function
if plot_data == True:
# Automatically determine frame number
f_num = len(data_dates)
# Declare list of point objects
points = list()
# Create a placeholder plot point
x,y = m(0,0)
# Fill list with same number of placeholders as points to animate
for i in range(len(data.lons)):
points.append(m.plot(x,y, 'ro', markersize=5)[0])
anim = animation.FuncAnimation(plt.gcf(), animate,
init_func=init, frames=f_num,
interval=500, blit=True)
# Save animation to file
anim.save('plot_animation.mp4', fps=f_num,
extra_args=['-vcodec', 'libx264'])
plt.show()
if __name__ == '__main__':
# WGS84 datum
wgs84 = pyproj.Proj(init='EPSG:4326')
# CSV data with columns 'daynr', 'lons', and 'lats'
datafile = '/home/dude/datalocations/data.csv'
data = readwhales(whale_datafile)
data_dates = np.unique(data.daynr).values
makeplot(plot_data=True)

Matplotlib Stackplot legend Error

I am trying to include a legend for my stackplot. I know that you cannot do it in the normal way so I followed the instructions from this similar post however there is still an error.
x=data[:,-1]
y1=map(int,data[:,1])
y2=map(int,data[:,2])
y3=map(int,data[:,3])
y4=map(int,data[:,4])
y5=map(int,data[:,5])
y6=map(int,data[:,6])
y7=map(int,data[:,7])
y8=map(int,data[:,8])
y9=map(int,data[:,9])
y10=map(int,data[:,0])
xnew=np.linspace(0,len(x),50)
smooth_y1=spline(np.arange(len(x)),y1,xnew)
smooth_y2=spline(np.arange(len(x)),y2,xnew)
smooth_y3=spline(np.arange(len(x)),y3,xnew)
smooth_y4=spline(np.arange(len(x)),y4,xnew)
smooth_y5=spline(np.arange(len(x)),y5,xnew)
smooth_y6=spline(np.arange(len(x)),y6,xnew)
smooth_y7=spline(np.arange(len(x)),y7,xnew)
smooth_y8=spline(np.arange(len(x)),y8,xnew)
smooth_y9=spline(np.arange(len(x)),y9,xnew)
smooth_y10=spline(np.arange(len(x)),y10,xnew)
plt.stackplot(np.arange(50),smooth_y1,smooth_y2,smooth_y3,smooth_y4,smooth_y5,smooth_y6,smooth_y7,smooth_y8,smooth_y9,smooth_y10)
plt.ylim([0,30])
plt.legend([smooth_y1,smooth_y2,smooth_y3,smooth_y4,smooth_y5,smooth_y6,smooth_y7,smooth_y8,smooth_y9,smooth_y10],['hfsdkjfhs','sldjfhsdkj','sdrtryf','sdfsd','sdkjf','sdfsd','sdrtdf','sfsd','sdaaafs','sdffghs'])
plt.show()
However an error occurs on the line with the legend. It says
File "C:\Python27\lib\site-packages\matplotlib\pyplot.py", line 3381, in legend
ret = gca().legend(*args, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\axes.py", line 4778, in legend
self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
File "C:\Python27\lib\site-packages\matplotlib\legend.py", line 366, in __init__
self._init_legend_box(handles, labels)
File "C:\Python27\lib\site-packages\matplotlib\legend.py", line 606, in _init_legend_box
handler = self.get_legend_handler(legend_handler_map, orig_handle)
File "C:\Python27\lib\site-packages\matplotlib\legend.py", line 546, in get_legend_handler
if orig_handle in legend_handler_keys:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
Does anyone know how to solve this?
from matplotlib.patches import Rectangle
label_list = ['hfsdkjfhs','sldjfhsdkj','sdrtryf','sdfsd','sdkjf','sdfsd','sdrtdf','sfsd','sdaaafs','sdffghs']
x = data[:, -1]
xnew=np.linspace(0,len(x),50)
smoothed_data = [spline(np.arange(len(x)),data[:, j%10],xnew)
for j in range(1, 11)]
# get a figure and axes
fig, ax = plt.subplots()
# make the stack plot
stack_coll = ax.stackplot(xnew, smoothed_data)
# set the ylim
ax.set_ylim([0,30])
# make proxy artists
proxy_rects = [Rectangle((0, 0), 1, 1, fc=pc.get_facecolor()[0]) for pc in stack_coll]
# make the legend
ax.legend(proxy_rects, label_list)
# re-draw the canvas
plt.draw()

Categories