Animate multiple points on Matplotlib Basemap over time - python

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)

Related

How to animate on matplotlib graph

I have an assignment where I need to project 3D cube in to 2D Cartesian plane, I've done plotting the vertex points but will still need to animate it somehow.
I have tried using FuncAnimation(), but no clue how it works. I am still new to python so go easy on me, thank you.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
A = np.array([-0.5,-0.5,-0.5])
B = np.array([0.5,-0.5,-0.5])
C = np.array([0.5,0.5,-0.5])
D = np.array([-0.5,0.5,-0.5])
E = np.array([-0.5,-0.5,0.5])
F = np.array([0.5,-0.5,0.5])
G = np.array([0.5,0.5,0.5])
H = np.array([-0.5,0.5,0.5])
load = np.array([A,B,C,D,E,F,G,H])
print(load)
fig = plt.figure()
ax = plt.axes(xlim =(-1,1),ylim =(-1,1))
# Declared to allow for x and y axis only
projection = np.array([ [1,0,0], [0,1,0] ])
xdata,ydata = [],[]
plt.title("Render 3D Cube in 2D Space")
for x in load:
for angle in range(360):
rotationY = np.array([ [np.cos(angle),0,np.sin(angle)],
[0,1,0],
[-np.sin(angle),0,np.cos(angle)] ])
rotationX = np.array([ [1,0,0],
[0,np.cos(angle),-np.sin(angle)],
[0,np.sin(angle),np.cos(angle)] ])
# Drawing each points
rotated = np.dot(rotationY,x)
rotated = np.dot(rotationX,rotated)
projected2d = np.dot(projection,rotated)
#projected2d = np.dot(projection,x) -With no rotations
line = ax.plot(projected2d[0],projected2d[1],c = "blue",marker = "o")
def animate(i):
x0,y0 = i
xdata.append(x0)
ydata.append(y0)
line.set_data(xdata,ydata)
return line
anim = FuncAnimation(fig,animate,interval =200,frames = 30)
plt.grid()
#plt.draw()
plt.show()
https://imgur.com/LR6oPtt
The FuncAnimation constructor takes a callable function (in your case animate) which gets the current frame number as an argument (here i) and updates the plot. This means, you should store all your intermediate points in an array (frames) and then later access those (you could also compute the projection on the fly, but I would not recommend this). The animation will then loop through the frames and apply the function to every frame.
Also, you should use radians (angles from 0 to 2π) for your rotations.
Here's a version that should work:
# list of the angles in radians
angles = np.linspace(0, 2*np.pi, 360)
# storage of single frames - one value per point and angle.
frames = np.zeros((len(load),len(angles),2))
# loops through all points and angles to store for later usage.
for i, x in enumerate(load):
for j, angle in enumerate(angles):
rotationY = np.array([[np.cos(angle),0,np.sin(angle)],
[0,1,0],
[-np.sin(angle),0,np.cos(angle)] ])
rotationX = np.array([ [1,0,0],
[0,np.cos(angle),-np.sin(angle)],
[0,np.sin(angle),np.cos(angle)] ])
rotated = np.dot(rotationY, x)
rotated = np.dot(rotationX, rotated)
projected2d = np.dot(projection, rotated)
# store the point.
frames[i,j,:] = projected2d
# draws the initial point.
line, = ax.plot(frames[:,0,0], frames[:,0,1], c="blue", marker="o", ls='')
# defines what happens at frame 'i' - you want to update with the current
# frame that we have stored before.
def animate(i):
line.set_data(frames[:,i,0], frames[:,i,1])
return line # not really necessary, but optional for blit algorithm
# the number of frames is the number of angles that we wanted.
anim = FuncAnimation(fig, animate, interval=200, frames=len(angles))

Matplotlib Affine2D object has no attribute 'skew_deg'

I am trying to create a plot using matplotlib but I get an error, and after hours of searching, I do not see an alternative or something that works. Here's the code that's giving me trouble:
import matplotlib.transforms as transforms
self.transDataToAxes = self.transScale + (self.transLimits +
transforms.Affine2D().skew_deg(rot, 0))
Which gives me the error: AttributeError: 'Affine2D' object has no attribute 'skew_deg'. This error happens with both python 2.7 and python 3.
If anyone has any suggestions on what I can try, it would be greatly appreciated.
Edit: Here is the entire script which I am trying to run, it should also be noted that I've tried this on Windows, Linux, and Mac with no success:
import matplotlib
spc_file = open('1OUN.txt', 'r').read()
import sharppy
import sharppy.sharptab.profile as profile
import sharppy.sharptab.interp as interp
import sharppy.sharptab.winds as winds
import sharppy.sharptab.utils as utils
import sharppy.sharptab.params as params
import sharppy.sharptab.thermo as thermo
import numpy as np
from StringIO import StringIO
def parseSPC(spc_file):
## read in the file
data = np.array([l.strip() for l in spc_file.split('\n')])
## necessary index points
title_idx = np.where( data == '%TITLE%')[0][0]
start_idx = np.where( data == '%RAW%' )[0] + 1
finish_idx = np.where( data == '%END%')[0]
## create the plot title
data_header = data[title_idx + 1].split()
location = data_header[0]
time = data_header[1][:11]
## put it all together for StringIO
full_data = '\n'.join(data[start_idx : finish_idx][:])
sound_data = StringIO( full_data )
## read the data into arrays
p, h, T, Td, wdir, wspd = np.genfromtxt( sound_data, delimiter=',', comments="%", unpack=True )
return p, h, T, Td, wdir, wspd
pres, hght, tmpc, dwpc, wdir, wspd = parseSPC(spc_file)
prof = profile.create_profile(profile='default', pres=pres, hght=hght, tmpc=tmpc, \
dwpc=dwpc, wspd=wspd, wdir=wdir, missing=-9999, strictQC=True)
import matplotlib.pyplot as plt
plt.plot(prof.tmpc, prof.hght, 'r-')
plt.plot(prof.dwpc, prof.hght, 'g-')
#plt.barbs(40*np.ones(len(prof.hght)), prof.hght, prof.u, prof.v)
plt.xlabel("Temperature [C]")
plt.ylabel("Height [m above MSL]")
plt.grid()
plt.show()
msl_hght = prof.hght[prof.sfc] # Grab the surface height value
agl_hght = interp.to_agl(prof, msl_hght) # Converts to AGL
msl_hght = interp.to_msl(prof, agl_hght) # Converts to MSL
plt.plot(thermo.ktoc(prof.thetae), prof.hght, 'r-', label='Theta-E')
plt.plot(prof.wetbulb, prof.hght, 'c-', label='Wetbulb')
plt.xlabel("Temperature [C]")
plt.ylabel("Height [m above MSL]")
plt.legend()
plt.grid()
plt.show()
sfcpcl = params.parcelx( prof, flag=1 ) # Surface Parcel
#fcstpcl = params.parcelx( prof, flag=2 ) # Forecast Parcel
#mupcl = params.parcelx( prof, flag=3 ) # Most-Unstable Parcel
#mlpcl = params.parcelx( prof, flag=4 ) # 100 mb Mean Layer Parcel
# This serves as an intensive exercise of matplotlib's transforms
# and custom projection API. This example produces a so-called
# SkewT-logP diagram, which is a common plot in meteorology for
# displaying vertical profiles of temperature. As far as matplotlib is
# concerned, the complexity comes from having X and Y axes that are
# not orthogonal. This is handled by including a skew component to the
# basic Axes transforms. Additional complexity comes in handling the
# fact that the upper and lower X-axes have different data ranges, which
# necessitates a bunch of custom classes for ticks,spines, and the axis
# to handle this.
from matplotlib.axes import Axes
import matplotlib.transforms as transforms
import matplotlib.axis as maxis
import matplotlib.spines as mspines
import matplotlib.path as mpath
from matplotlib.projections import register_projection
# The sole purpose of this class is to look at the upper, lower, or total
# interval as appropriate and see what parts of the tick to draw, if any.
class SkewXTick(maxis.XTick):
def draw(self, renderer):
if not self.get_visible(): return
renderer.open_group(self.__name__)
lower_interval = self.axes.xaxis.lower_interval
upper_interval = self.axes.xaxis.upper_interval
if self.gridOn and transforms.interval_contains(
self.axes.xaxis.get_view_interval(), self.get_loc()):
self.gridline.draw(renderer)
if transforms.interval_contains(lower_interval, self.get_loc()):
if self.tick1On:
self.tick1line.draw(renderer)
if self.label1On:
self.label1.draw(renderer)
if transforms.interval_contains(upper_interval, self.get_loc()):
if self.tick2On:
self.tick2line.draw(renderer)
if self.label2On:
self.label2.draw(renderer)
renderer.close_group(self.__name__)
# This class exists to provide two separate sets of intervals to the tick,
# as well as create instances of the custom tick
class SkewXAxis(maxis.XAxis):
def __init__(self, *args, **kwargs):
maxis.XAxis.__init__(self, *args, **kwargs)
self.upper_interval = 0.0, 1.0
def _get_tick(self, major):
return SkewXTick(self.axes, 0, '', major=major)
#property
def lower_interval(self):
return self.axes.viewLim.intervalx
def get_view_interval(self):
return self.upper_interval[0], self.axes.viewLim.intervalx[1]
# This class exists to calculate the separate data range of the
# upper X-axis and draw the spine there. It also provides this range
# to the X-axis artist for ticking and gridlines
class SkewSpine(mspines.Spine):
def _adjust_location(self):
trans = self.axes.transDataToAxes.inverted()
if self.spine_type == 'top':
yloc = 1.0
else:
yloc = 0.0
left = trans.transform_point((0.0, yloc))[0]
right = trans.transform_point((1.0, yloc))[0]
pts = self._path.vertices
pts[0, 0] = left
pts[1, 0] = right
self.axis.upper_interval = (left, right)
# This class handles registration of the skew-xaxes as a projection as well
# as setting up the appropriate transformations. It also overrides standard
# spines and axes instances as appropriate.
class SkewXAxes(Axes):
# The projection must specify a name. This will be used be the
# user to select the projection, i.e. ``subplot(111,
# projection='skewx')``.
name = 'skewx'
def _init_axis(self):
#Taken from Axes and modified to use our modified X-axis
self.xaxis = SkewXAxis(self)
self.spines['top'].register_axis(self.xaxis)
self.spines['bottom'].register_axis(self.xaxis)
self.yaxis = maxis.YAxis(self)
self.spines['left'].register_axis(self.yaxis)
self.spines['right'].register_axis(self.yaxis)
def _gen_axes_spines(self):
spines = {'top':SkewSpine.linear_spine(self, 'top'),
'bottom':mspines.Spine.linear_spine(self, 'bottom'),
'left':mspines.Spine.linear_spine(self, 'left'),
'right':mspines.Spine.linear_spine(self, 'right')}
return spines
def _set_lim_and_transforms(self):
"""
This is called once when the plot is created to set up all the
transforms for the data, text and grids.
"""
rot = 30
#Get the standard transform setup from the Axes base class
Axes._set_lim_and_transforms(self)
# Need to put the skew in the middle, after the scale and limits,
# but before the transAxes. This way, the skew is done in Axes
# coordinates thus performing the transform around the proper origin
# We keep the pre-transAxes transform around for other users, like the
# spines for finding bounds
self.transDataToAxes = self.transScale + (self.transLimits +
transforms.Affine2D().skew_deg(rot, 0))
# Create the full transform from Data to Pixels
self.transData = self.transDataToAxes + self.transAxes
# Blended transforms like this need to have the skewing applied using
# both axes, in axes coords like before.
self._xaxis_transform = (transforms.blended_transform_factory(
self.transScale + self.transLimits,
transforms.IdentityTransform()) +
transforms.Affine2D().skew_deg(rot, 0)) + self.transAxes
# Now register the projection with matplotlib so the user can select
# it.
register_projection(SkewXAxes)
pcl = sfcpcl
# Create a new figure. The dimensions here give a good aspect ratio
fig = plt.figure(figsize=(6.5875, 6.2125))
ax = fig.add_subplot(111, projection='skewx')
ax.grid(True)
pmax = 1000
pmin = 10
dp = -10
presvals = np.arange(int(pmax), int(pmin)+dp, dp)
# plot the moist-adiabats
for t in np.arange(-10,45,5):
tw = []
for p in presvals:
tw.append(thermo.wetlift(1000., t, p))
ax.semilogy(tw, presvals, 'k-', alpha=.2)
def thetas(theta, presvals):
return ((theta + thermo.ZEROCNK) / (np.power((1000. / presvals),thermo.ROCP))) - thermo.ZEROCNK
# plot the dry adiabats
for t in np.arange(-50,110,10):
ax.semilogy(thetas(t, presvals), presvals, 'r-', alpha=.2)
plt.title(' OAX 140616/1900 (Observed)', fontsize=14, loc='left')
# Plot the data using normal plotting functions, in this case using
# log scaling in Y, as dicatated by the typical meteorological plot
ax.semilogy(prof.tmpc, prof.pres, 'r', lw=2)
ax.semilogy(prof.dwpc, prof.pres, 'g', lw=2)
ax.semilogy(pcl.ttrace, pcl.ptrace, 'k-.', lw=2)
# An example of a slanted line at constant X
l = ax.axvline(0, color='b', linestyle='--')
l = ax.axvline(-20, color='b', linestyle='--')
# Disables the log-formatting that comes with semilogy
ax.yaxis.set_major_formatter(plt.ScalarFormatter())
ax.set_yticks(np.linspace(100,1000,10))
ax.set_ylim(1050,100)
ax.xaxis.set_major_locator(plt.MultipleLocator(10))
ax.set_xlim(-50,50)
plt.show()
And the text file can be found here, before renaming it: OUN_Sounding

Animation in matplotlib with scatter and using set_offsets: How to update color of scatter point

I am trying to create an animation that plots GPS data from a run. I have code that plots the points and creates the animation. In order to have the old point deleted and the new plotted with each frame I am using scat.set_offsets(x[i],y[i]) to plot the points. The trouble I am running into is that I want the color of the point to reflect the speed at that position. The code below works but I cannot figure out the color updating. I have tried using scat.alpha() but it switches the color once and never updates again. I want to use, jet, for example, and have 0 mph be blue and the fastest time to be red.
The running log has the first two columns as lon/lat and the third is velocity. Some data:
Longitude Latitude GPS Speed(km/h)
-88.19895578 43.19975045 0
-88.19894667 43.19976286 0
-88.19893236 43.19977277 0
-88.19872605 43.19969481 9.9
-88.19871956 43.19967161 10.799999
-88.19872339 43.19962663 11.7
-88.19873801 43.19959561 11.7
-88.19879232 43.19958254 9.9
-88.19876674 43.1995382 9.9
-88.19876797 43.19948614 7.2
The code is below. Thanks for the help!
"""
Matplotlib Animation of Accelerometer Vectors
author: Ryan Croke
website: http://TheHolyMath.com
"""
import numpy as np
import networkx as nx
from matplotlib import pyplot as plt
from matplotlib import animation
from matplotlib import cm
import csv
import json
import smopy
import re
### DATA ###
# Read shape file
g = nx.read_shp("tl_2014_us_state/tl_2014_us_state.shp")
# plot points of acceleration and deceleration
f = open('runninglog.csv')
cr = csv.reader(f)
# Make arrays for processing
latitude = []
longitude = []
velocity = []
# Velocity is in m/s - convert to miles/hour
# 1m/s = 2.236 mph
for row in cr:
# Need to test for headers - pass anything with letters
if any(c.isalpha() for c in row[0]) == True:
pass
else:
longitude.append(float(row[0]))
if any(c.isalpha() for c in row[1]) == True:
pass
else:
latitude.append(float(row[1]))
if any(c.isalpha() for c in row[2]) == True:
pass
else:
velocity.append(round(float(row[2])*2.236,2))
# Numer of plots
P = int(len(latitude))
# In the USA longitude is decreasing from west to east, latitude is increasing from south to north
# lower left will be max(lon) and min(lat)
pos1 = (float(min(latitude)),float(max(longitude)))
pos2 = (float(max(latitude)),float(min(longitude)))
### Animation ###
# First set up the figure, the axis, and the plot element we want to animate
map = smopy.Map(pos1, pos2, z=15)
# Smoopy allows you to create a matplotlib image using a t0_pixels command
x = []
y = []
# Create array's for lat/lon in pixel image
for i in xrange(len(latitude)):
x1, y1 = map.to_pixels(latitude[i],longitude[i])
x.append(x1)
y.append(y1)
# animation of a scatter plot using x, y from above
#------------------------------------------------------------------------------
fig = plt.figure()
ax = map.show_mpl(figsize=(8, 6))
scat = ax.scatter([],[],s=60)
# initialization function: plot the background of each frame
def init():
scat.set_offsets([])
#scat.set_facecolor([])
return scat,
# animation function. This is called sequentially
def animate(i):
scat.set_offsets([x[i], y[i]])
#scat.set_edgecolors('red')
#scat.set_facecolor()
return scat,
# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=P, interval=40, repeat=False, blit=True)
# save the animation as an mp4. This requires ffmpeg or mencoder to be
# installed. The extra_args ensure that the x264 codec is used, so that
# the video can be embedded in html5. You may need to adjust this for
# your system: for more information, see
# http://matplotlib.sourceforge.net/api/animation_api.html
anim.save('Around_the_block_animation.mp4', fps=30, extra_args=['-vcodec', 'libx264'])
plt.show()

Animation of pcolormesh() with mpl_toolkit.basemap giving attribute error

I am trying to animate some density data on a basemap map. Following an approach as was done in [this SO question][1], I get the following error:
/usr/local/lib/python2.7/dist-packages/matplotlib/collections.pyc in update_scalarmappable(self)
627 if self._A is None:
628 return
--> 629 if self._A.ndim > 1:
630 raise ValueError('Collections can only map rank 1 arrays')
631 if not self.check_update("array"):
AttributeError: 'list' object has no attribute 'ndim'
If I instead set the data in init() with null values by self.quad.set_array(self.z.ravel()), I end up with two plotted maps with no data being animated.
Any light that anybody could shed on what I'm doing wrong would be greatly appreciated. Thanks!
example code:
def plot_pcolor(lons,lats):
class UpdateQuad(object):
def __init__(self,ax, map_object, lons, lats):
self.ax = ax
self.m = map_object
self.lons = lons
self.lats = lats
self.ydim, self.xdim = lons.shape
self.z = np.zeros((self.ydim-1,self.xdim-1))
x, y = self.m(lons, lats)
self.quad = ax.pcolormesh(x, y, self.z, cmap=plt.cm.Reds)
def init(self):
print 'update init'
self.quad.set_array([])
return self.quad
def __call__(self,i):
data = np.zeros((self.ydim-1,self.xdim-1))
for i in range(self.ydim-1):
for j in range(self.xdim-1):
data[i,j]=random.random()+4
self.quad.set_array(data.ravel())
return self.quad
fig = plt.figure()
ax = fig.add_axes([0.1,0.1,0.8,0.8])
m = Basemap(width=2000000,height=2000000,
resolution='l', projection='laea',\
lat_ts=10.,\
lat_0=64.,lon_0=10., ax=ax)
m.fillcontinents()
ud = UpdateQuad(ax, m, lons, lats)
anim = animation.FuncAnimation(fig, ud, init_func=ud.init,
frames=20, blit=False)
plt.show()
if __name__ == '__main__':
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.basemap import Basemap
import numpy as np
import random
lons = np.linspace(-5.,25., num = 25)[:50]
lats = np.linspace(56., 71., num = 25)[:50]
lons,lats = np.meshgrid(lons,lats)
plot_pcolor(lons,lats)
It looks like the set_data method should require an ndarray (not sure why the example I had followed was working correctly).
So in the init() function, you should use quad.set_array(np.array([])) rather than quad.set_array([]).
Other problems to be aware of:
As mentioned before, you also want set blit=False in your FuncAnimation() call.
I was also experiencing problems when I set the quad artist attribute animated to True. Leave that be (i.e. quad.set_animated(False), which is the default anyway).
If you do not specify the bounds via norm in your first pcolormesh() call, it will set them according to the data you pass (in my case null), which resulting in my getting blank animations. Setting them according to the data you will animate later in the initial call prevented this problem in my case.
pcolormesh() takes the bounding positions to the data field, which should be +1 in the y and x dimension of the data array. If the data array is equal (or greater than) the dimensions of the position data, pcolormesh() will omit any data outside of this boundary requirement. I thought that my data would just appear offset by one grid cell, but everything was all whacky before I passed the correct boundary positions. See another question of mine for calculating these HERE.
Older versions of matplotlib do not have very good error reporting. I recommend upgrading to the latest version if that is an option for you.
Some random trouble-shooting:
After updating matplotlib and basemap and attempting to implement this in my existing plotting routine, I received the following error:
ValueError: All values in the dash list must be positive
I first thought it had to do with my pcolormesh() objects, but it took me way too long to discover that it was due to my previous setting of the dash attribute in my m.drawmeridians() call to dashes=[1,0] for a solid meridian. In the new version of matplotlib the handling of dashes was changed to give this error. The new prefered method for setting a solid line for the dash attribute is dashes=(None,None), which I don't like.
Resulting animation:
Code example for above output:
def plot_pcolor(lons,lats):
class UpdateQuad(object):
def __init__(self,ax, map_object, lons, lats):
self.ax = ax
self.m = map_object
self.lons = lons
self.lats = lats
vmin = 0
vmax = 1
self.ydim, self.xdim = lons.shape
self.z = np.zeros((self.ydim-1,self.xdim-1))
levels = MaxNLocator(nbins=15).tick_values(vmin,vmax)
cmap = plt.cm.cool
norm = BoundaryNorm(levels, ncolors=cmap.N, clip=True)
x, y = self.m(lons, lats)
self.quad = self.ax.pcolormesh(x, y, self.z, alpha=0.9,
norm=norm, cmap=cmap,
vmin=vmin, vmax=vmax)
def init(self):
print 'update init'
self.quad.set_array(np.asarray([]))
return self.quad
def __call__(self,i):
for i in range(self.ydim-1):
for j in range(self.xdim-1):
self.z[i,j]=random.random()
self.quad.set_array(self.z.ravel())
return self.quad
fig, ax = plt.subplots()
m = Basemap(width=2000000,height=2000000,
resolution='l', projection='laea',\
lat_ts=10.,\
lat_0=64.,lon_0=10., ax=ax)
m.fillcontinents()
ud = UpdateQuad(ax, m, lons, lats)
anim = animation.FuncAnimation(fig, ud, init_func=ud.init,
frames=20, blit=False)
fig.tight_layout()
plt.show()
return ud.quad
if __name__ == '__main__':
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.basemap import Basemap
import numpy as np
import random
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import MaxNLocator
lons = np.linspace(-5.,25., num = 25)[:50]
lats = np.linspace(56., 71., num = 25)[:50]
lons,lats = np.meshgrid(lons,lats)
quad = plot_pcolor(lons,lats)

3D animation with matplotlib, connect points to create moving stick figure

I am currently having some trouble with my code which animates some time-series data, and I cannot quite figure it out. Basically I have 12 tags which I am animating through time. Each tag has a trajectory in time such that the movement path can be seen for each tag as it progresses (have a look at the image attached). Now I would like the animation to also include the lines between pairs of tags (i.e. pairs of points - for example, how to add an animation line between the yellow and green tags), but I am not entirely sure how to do this. This is code adapted from jakevdp.github.io.
Here is the code thus far.
"""
Full animation of a walking event (note: a lot of missing data)
"""
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('TkAgg') # Need to use in order to run on mac
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.colors import cnames
from matplotlib import animation
#=============================================================================================
t_start = 1917 # start frame
t_end = 2130 # end frame
data = pd.read_csv('~/Smart-first_phase_NaN-zeros.csv') # only coordinate data
df = data.loc[t_start:t_end,'Shoulder_left_x':'Ankle_right_z']
# Find max and min values for animation ranges
df_minmax = pd.DataFrame(index=list('xyz'),columns=range(2))
for i in list('xyz'):
c_max = df.filter(regex='_{}'.format(i)).max().max()
c_min = df.filter(regex='_{}'.format(i)).min().min()
df_minmax.ix[i] = np.array([c_min,c_max])
df_minmax = 1.3*df_minmax # increase by 30% to make animation look better
df.columns = np.repeat(range(12),3) # store cols like this for simplicity
N_tag = df.shape[1]/3 # nr of tags used (all)
N_trajectories = N_tag
t = np.linspace(0,data.Time[t_end],df.shape[0]) # pseudo time-vector for first walking activity
x_t = np.zeros(shape=(N_tag,df.shape[0],3)) # empty animation array (3D)
for tag in range(12):
# store data in numpy 3D array: (tag,time-stamp,xyz-coordinates)
x_t[tag,:,:] = df[tag]
#===STICK-LINES========================================================================================
#xx = [x_t[1,:,0],x_t[2,:,0]]
#yy = [x_t[1,:,1],x_t[2,:,1]]
#zz = [x_t[1,:,2],x_t[2,:,2]]
#======================================================================================================
# Set up figure & 3D axis for animation
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
ax.axis('on')
# choose a different color for each trajectory
colors = plt.cm.jet(np.linspace(0, 1, N_trajectories))
# set up trajectory lines
lines = sum([ax.plot([], [], [], '-', c=c) for c in colors], [])
# set up points
pts = sum([ax.plot([], [], [], 'o', c=c) for c in colors], [])
# set up lines which create the stick figures
stick_lines = sum([ax.plot([], [], [], '-', c=c) for c in colors], [])
# prepare the axes limits
ax.set_xlim(df_minmax.ix['x'].values)
ax.set_ylim(df_minmax.ix['z'].values) # note usage of z coordinate
ax.set_zlim(df_minmax.ix['y'].values) # note usage of y coordinate
# set point-of-view: specified by (altitude degrees, azimuth degrees)
ax.view_init(30, 0)
# initialization function: plot the background of each frame
def init():
for line, pt, stick_line in zip(lines, pts, stick_lines):
# trajectory lines
line.set_data([], [])
line.set_3d_properties([])
# points
pt.set_data([], [])
pt.set_3d_properties([])
# stick lines
stick_line.set_data([], [])
stick_line.set_3d_properties([])
return lines + pts + stick_lines
# animation function. This will be called sequentially with the frame number
def animate(i):
# we'll step two time-steps per frame. This leads to nice results.
i = (5 * i) % x_t.shape[1]
for line, pt, stick_line, xi in zip(lines, pts, stick_lines, x_t):
x, z, y = xi[:i].T # note ordering of points to line up with true exogenous registration (x,z,y)
# trajectory lines
line.set_data(x,y)
line.set_3d_properties(z)
# points
pt.set_data(x[-1:], y[-1:])
pt.set_3d_properties(z[-1:])
# stick lines
#stick_line.set_data(xx,zz)
#stick_line.set_3d_properties(yy)
ax.view_init(30, 0.3 * i)
fig.canvas.draw()
return lines + pts + stick_lines
# instantiate the animator.
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=500, interval=30, blit=True)
# Save as mp4. This requires mplayer or ffmpeg to be installed
#anim.save('lorentz_attractor.mp4', fps=15, extra_args=['-vcodec', 'libx264'])
plt.show()
So, to conclude: I would like lines that moves with the point pairs (orange, yellow) and (yellow, green). If someone could show me how to do that I should be able to extrapolate the methods to the rest of the animation.
As ever, any help is much appreciated.
The original data can be found here, if anyone wants to replicate: https://www.dropbox.com/sh/80f8ue4ffa4067t/Pntl5-gUW4
EDIT: IMPLEMENTED SOLUTION
Here is the final result, using the proposed solution.
I modified your code to add stick lines, but to simplify the code, I removed the trace lines:
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('TkAgg') # Need to use in order to run on mac
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.colors import cnames
from matplotlib import animation
#=============================================================================================
t_start = 1917 # start frame
t_end = 2130 # end frame
data = pd.read_csv('Smart-first_phase_NaN-zeros.csv') # only coordinate data
df = data.loc[t_start:t_end,'Shoulder_left_x':'Ankle_right_z']
# Find max and min values for animation ranges
df_minmax = pd.DataFrame(index=list('xyz'),columns=range(2))
for i in list('xyz'):
c_max = df.filter(regex='_{}'.format(i)).max().max()
c_min = df.filter(regex='_{}'.format(i)).min().min()
df_minmax.ix[i] = np.array([c_min,c_max])
df_minmax = 1.3*df_minmax # increase by 30% to make animation look better
df.columns = np.repeat(range(12),3) # store cols like this for simplicity
N_tag = df.shape[1]/3 # nr of tags used (all)
N_trajectories = N_tag
t = np.linspace(0,data.Time[t_end],df.shape[0]) # pseudo time-vector for first walking activity
x_t = np.zeros(shape=(N_tag,df.shape[0],3)) # empty animation array (3D)
for tag in range(12):
# store data in numpy 3D array: (tag,time-stamp,xyz-coordinates)
x_t[tag,:,:] = df[tag]
x_t = x_t[:, :, [0, 2, 1]]
# Set up figure & 3D axis for animation
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
ax.axis('on')
# choose a different color for each trajectory
colors = plt.cm.jet(np.linspace(0, 1, N_trajectories))
# set up trajectory lines
lines = sum([ax.plot([], [], [], '-', c=c) for c in colors], [])
# set up points
pts = sum([ax.plot([], [], [], 'o', c=c) for c in colors], [])
# set up lines which create the stick figures
stick_defines = [
(0, 1),
(1, 2),
(3, 4),
(4, 5),
(6, 7),
(7, 8),
(9, 10),
(10, 11)
]
stick_lines = [ax.plot([], [], [], 'k-')[0] for _ in stick_defines]
# prepare the axes limits
ax.set_xlim(df_minmax.ix['x'].values)
ax.set_ylim(df_minmax.ix['z'].values) # note usage of z coordinate
ax.set_zlim(df_minmax.ix['y'].values) # note usage of y coordinate
# set point-of-view: specified by (altitude degrees, azimuth degrees)
ax.view_init(30, 0)
# initialization function: plot the background of each frame
def init():
for line, pt in zip(lines, pts):
# trajectory lines
line.set_data([], [])
line.set_3d_properties([])
# points
pt.set_data([], [])
pt.set_3d_properties([])
return lines + pts + stick_lines
# animation function. This will be called sequentially with the frame number
def animate(i):
# we'll step two time-steps per frame. This leads to nice results.
i = (5 * i) % x_t.shape[1]
for line, pt, xi in zip(lines, pts, x_t):
x, y, z = xi[:i].T # note ordering of points to line up with true exogenous registration (x,z,y)
pt.set_data(x[-1:], y[-1:])
pt.set_3d_properties(z[-1:])
for stick_line, (sp, ep) in zip(stick_lines, stick_defines):
stick_line._verts3d = x_t[[sp,ep], i, :].T.tolist()
ax.view_init(30, 0.3 * i)
fig.canvas.draw()
return lines + pts + stick_lines
# instantiate the animator.
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=500, interval=30, blit=True)
plt.show()
Here is one frame of the animation:

Categories