Why does FuncAnimation revert back to the origin? - matplotlib

I am trying to animate a sample path of Brownian motion by using FuncAnimation, but the animation keeps reverting back to the origin.
Here is my code.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# create the time interval and partition
t = 2.5
n = 100
# How many sample paths?
path_amt = 2
# Create a brownian sample path
def bsp(t, n):
dB = np.sqrt(t / n) * np.random.normal(0, 1, size=n)
B = np.zeros(n+1)
B[1:] = np.cumsum(dB)
return(B)
# Simulate "path_amt" sample paths
def sample_paths(i, t ,n):
BSP = np.zeros((i, n+1))
for k in range(i):
BSP[k,:] = bsp(t, n)
return(BSP)
B_paths = sample_paths(path_amt, t, n)
This part is essentially just coming up with two independent Browinan motions. Each Brownian motion is a 1-d array of length n+1. I then store the two brownian motions in a (2, n+1) array titled B_paths, so each row represents a brownian motion. Here is the code for the animation.
# Create the animation function for the sample path
x = []
y = []
t_axis = np.linspace(0, t, n+1)
fig, ax = plt.subplots()
ax.set_xlim(0, 3)
ax.set_ylim(-4, 4)
line, = ax.plot(0, 0)
def anim_func(i):
x.append(t_axis[int(i * n / t)])
y.append(B_paths[0][int(i * n / t)])
line.set_xdata(x)
line.set_ydata(y)
return line,
animation = FuncAnimation(fig, func = anim_func, \
frames = np.linspace(0, t, n+1), interval = 10)
plt.show()

Because the animation is looping. Once frame reaches t=2.5, then it starts over, but inside your anim_func you don't clear x, y.
You can either modify this function:
def anim_func(i):
x.append(t_axis[int(i * n / t)])
y.append(B_paths[0][int(i * n / t)])
line.set_xdata(x)
line.set_ydata(y)
if i == t:
x.clear()
y.clear()
return line,
Or set repeat=False in the FuncAnimation call.

Related

My animated function will return the right values but it won't plot them

I am trying to make an animated plot of a planet's motion with only the planet moving but when returning the ln in my update function it won't plot the point. I am printing the r and theta values and they are correct in the function so I am not sure where the error is.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, writers
#Polar stuff
fig = plt.figure(figsize=(6,6))
ax = plt.subplot(111, polar=True)
ax.set_ylim(0,1)
line, = ax.plot(0, 0)
r = []
theta = []
c=0
# Animation requirements.
ln, = plt.plot([], [], 'r:',
markersize=1.5,
alpha=1,
animated=True)
semi_major_axis = 2
eccentricity = 0.5
omega = np.linspace(0,2*np.pi, num = 50)
aphelion = semi_major_axis*(1+eccentricity)
ax.set_rticks(np.linspace(0,aphelion+1, num = 5))
def init():
ax.set_ylim(0,(aphelion+1))
#ax.set_rticks(np.linspace(0,aphelion+1, num = 5))
return ln,
def update(frame):
r= frame
theta= (semi_major_axis * (1 - eccentricity ** 2) / (1 - eccentricity * np.cos(frame)))
ln.set_data(r, theta)
print(r,theta)
return ln,
ani = FuncAnimation(fig, update, frames=omega, init_func=init, interval=500, blit=True,repeat=True)
I asked a similar question and I was able to get the accurate plot but I could not get the points to disappear so I did some more research on have this code. I can get it to plot multiple points by appending to r and theta and then plotting r[:],theta[:] but not one singular point. Even if I try r[c],theta[c] where c is a counting variable

How to make a gif out of subplot?

I'm using this code from matplotlib website to generate gif through list of images.
https://matplotlib.org/gallery/animation/dynamic_image2.html
However, I'm struggling to figure out how to make it work if I have subplot with two axes inside it. Thus, it is as if I have two images, which one should I append to the list?
EDIT: sample code:
ims = []
for i in range(60):
x += np.pi / 15.
y += np.pi / 20.
im = plt.imshow(f(x, y), animated=True)
ims.append([im])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)
As explained in the page you linked, the array of artists passed to ArtistAnimation is a list of lists, each element of the list corresponds to one frame, where all the elements of the "inner" lists are updated.
Therefore
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, (ax1, ax2) = plt.subplots(1,2)
def f(x, y):
return np.sin(x) + np.cos(y)
x = np.linspace(0, 2 * np.pi, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
# ims is a list of lists, each row is a list of artists to draw in the
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
for i in range(60):
x += np.pi / 15.
y += np.pi / 20.
im1 = ax1.imshow(f(x, y), animated=True)
im2 = ax2.imshow(np.random.random(size=(100,120)))
ims.append([im1,im2])
ani = animation.ArtistAnimation(fig, ims, interval=50, blit=True,
repeat_delay=1000)

Matplotlib: different scale on negative side of the axis

Background
I am trying to show three variables on a single plot. I have connected the three points using lines of different colours based on some other variables. This is shown here
Problem
What I want to do is to have a different scale on the negative x-axis. This would help me in providing positive x_ticks, different axis label and also clear and uncluttered representation of the lines on left side of the image
Question
How to have a different positive x-axis starting from 0 towards negative direction?
Have xticks based on data plotted in that direction
Have a separate xlabel for this new axis
Additional information
I have checked other questions regarding inclusion of multiple axes e.g. this and this. However, these questions did not serve the purpose.
Code Used
font_size = 20
plt.rcParams.update({'font.size': font_size})
fig = plt.figure()
ax = fig.add_subplot(111)
#read my_data from file or create it
for case in my_data:
#Iterating over my_data
if condition1 == True:
local_linestyle = '-'
local_color = 'r'
local_line_alpha = 0.6
elif condition2 == 1:
local_linestyle = '-'
local_color = 'b'
local_line_alpha = 0.6
else:
local_linestyle = '--'
local_color = 'g'
local_line_alpha = 0.6
datapoint = [case[0], case[1], case[2]]
plt.plot(datapoint[0], 0, color=local_color)
plt.plot(-datapoint[2], 0, color=local_color)
plt.plot(0, datapoint[1], color=local_color)
plt.plot([datapoint[0], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
plt.plot([-datapoint[2], 0], [0, datapoint[1]], linestyle=local_linestyle, color=local_color)
plt.show()
exit()
You can define a custom scale, where values below zero are scaled differently than those above zero.
import numpy as np
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import FuncFormatter
class AsymScale(mscale.ScaleBase):
name = 'asym'
def __init__(self, axis, **kwargs):
mscale.ScaleBase.__init__(self)
self.a = kwargs.get("a", 1)
def get_transform(self):
return self.AsymTrans(self.a)
def set_default_locators_and_formatters(self, axis):
# possibly, set a different locator and formatter here.
fmt = lambda x,pos: "{}".format(np.abs(x))
axis.set_major_formatter(FuncFormatter(fmt))
class AsymTrans(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
def __init__(self, a):
mtransforms.Transform.__init__(self)
self.a = a
def transform_non_affine(self, x):
return (x >= 0)*x + (x < 0)*x*self.a
def inverted(self):
return AsymScale.InvertedAsymTrans(self.a)
class InvertedAsymTrans(AsymTrans):
def transform_non_affine(self, x):
return (x >= 0)*x + (x < 0)*x/self.a
def inverted(self):
return AsymScale.AsymTrans(self.a)
Using this you would provide a scale parameter a that scales the negative part of the axes.
# Now that the Scale class has been defined, it must be registered so
# that ``matplotlib`` can find it.
mscale.register_scale(AsymScale)
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([-2, 0, 5], [0,1,0])
ax.set_xscale("asym", a=2)
ax.annotate("negative axis", xy=(.25,0), xytext=(0,-30),
xycoords = "axes fraction", textcoords="offset points", ha="center")
ax.annotate("positive axis", xy=(.75,0), xytext=(0,-30),
xycoords = "axes fraction", textcoords="offset points", ha="center")
plt.show()
The question is not very clear about what xticks and labels are desired, so I left that out for now.
Here's how to get what you want. This solution uses two twined axes object to get different scaling to the left and right of the origin, and then hides all the evidence:
import matplotlib.pyplot as plt
import matplotlib as mpl
from numbers import Number
tickkwargs = {m+k:False for k in ('bottom','top','left','right') for m in ('','label')}
p = np.zeros((10, 3, 2))
p[:,0,0] -= np.arange(10)*.1 + .5
p[:,1,1] += np.repeat(np.arange(5), 2)*.1 + .3
p[:,2,0] += np.arange(10)*.5 + 2
fig = plt.figure(figsize=(8,6))
host = fig.add_subplot(111)
par = host.twiny()
host.set_xlim(-6, 6)
par.set_xlim(-1, 1)
for ps in p:
# mask the points with negative x values
ppos = ps[ps[:,0] >= 0].T
host.plot(*ppos)
# mask the points with positive x values
pneg = ps[ps[:,0] <= 0].T
par.plot(*pneg)
# hide all possible ticks/notation text that could be set by the second x axis
par.tick_params(axis="both", **tickkwargs)
par.xaxis.get_offset_text().set_visible(False)
# fix the x tick labels so they're all positive
host.set_xticklabels(np.abs(host.get_xticks()))
fig.show()
Output:
Here's what the set of points p I used in the code above look like when plotted normally:
fig = plt.figure(figsize=(8,6))
ax = fig.gca()
for ps in p:
ax.plot(*ps.T)
fig.show()
Output:
The method of deriving a class of mscale.ScaleBase as shown in other answers may be too complicated for your purpose.
You can pass two scale transform functions to set_xscale or set_yscale, something like the following.
def get_scale(a=1): # a is the scale of your negative axis
def forward(x):
x = (x >= 0) * x + (x < 0) * x * a
return x
def inverse(x):
x = (x >= 0) * x + (x < 0) * x / a
return x
return forward, inverse
fig, ax = plt.subplots()
forward, inverse = get_scale(a=3)
ax.set_xscale('function', functions=(forward, inverse)) # this is for setting x axis
# do plotting
More examples can be found in this doc.

Picking a new color for each contour component in matplotlib

Sometimes a specific contour level has several components. For instance:
import numpy as np
import matplotlib.pyplot as plt
delta = 1./100
x = np.arange(-2.0, 3.0, delta)
y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z = Y * Y - X * X * X + X
plt.figure()
CS = plt.contour(X, Y, Z, [0])
How do I color each component using its own color?
I found a way to do it! :) But it's hacky, so I'll leave my answer un-accepted until someone comes up with a better way to do it. Here is my solution (matplotlib 1.4.3).
As noted in the comments, what I asked is not something that matplotlib.contour knows how to do. But after investigating the code a little bit I came up with a solution that works and isn't too bad.
Behind the scenes, a class called QuadContourSet is used to store all the contour paths in "line collections", one line collection per level. The line collections are styled all together.
My idea was to subclass this class and replace the function _get_allsegs_and_allkinds with a function that separates the line collections to one line collection per component, instead of per-level. This is hacky so I named it HackyContourSet but it's good enough for my purposes.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import contour
class HackyContourSet(contour.QuadContourSet):
def _get_allsegs_and_allkinds(self):
allkinds = None
allsegs = []
for level in self.levels:
nlist = self.Cntr.trace(level)
nseg = len(nlist) // 2
segs = nlist[:nseg]
# Original code: allsegs.append(segs) - put all level segments in a
# collection. New code: Put each segment in a separate collection.
for seg in segs:
allsegs.append([seg])
# The following line is needed to make QuadContourSet think there are
# more levels, so it would actually draw the additional collections.
self.levels = [0] * len(allsegs)
return allsegs, allkinds
####################
delta = 1./100
x = np.arange(-2.0, 3.0, delta)
y = np.arange(-3.0, 3.0, delta)
X, Y = np.meshgrid(x, y)
Z = Y * Y - X * X * X + X
plt.figure()
plt.cla()
axes = plt.gca()
CS = HackyContourSet(axes, X, Y, Z, [0], colors=list('rb'))

How to drop connecting lines where the function is discontinuous

I'm plotting some functions that have several discontinuities. Each function is given as a list. I want to connect points with lines only where the function is continuous.
Here is a simplified example of what plot is doing.
x=linspace(0,1,100)
y=zeros(100)
y[x<0.5] = x[x<0.5]
y[x>=0.5] = 1 + x[x>=0.5]
plot(x, y, '-o')
There is a discontinuity at x=0.5, but plot connects all points with lines regardless.
My functions are different of course. They typically have several discontinuities in different places. The criterion for the discontinuity is simple. Say, if the function jumps by more than 0.5, I assume it is discontinuous at that point.
Is there an option in plot to tell it to drop the connecting lines between the points where the function is discontinuous? I recall being able to do that easily with gnuplot.
use nan to break the line into multiple segments:
import numpy as np
from pylab import *
x=linspace(0,1,100)
y=zeros(100)
y[x<0.5] = x[x<0.5]
y[x>=0.5] = 1 + x[x>=0.5]
pos = np.where(np.abs(np.diff(y)) >= 0.5)[0]
x[pos] = np.nan
y[pos] = np.nan
plot(x, y, '-o')
Edit:
to insert nan at discontinuities:
pos = np.where(np.abs(np.diff(y)) >= 0.5)[0]+1
x = np.insert(x, pos, np.nan)
y = np.insert(y, pos, np.nan)
Here is my suggestion for plotting tan(x):
import matplotlib.pyplot as plt
from math import *
x_lim = 3*pi/2
y_lim = 5
n = 1000
X = []
Y = []
Z = []
for i in range(0,2*n):
x = -x_lim + i*x_lim/n
y = tan(x)
if y<y_lim and y>-y_lim:
X.append(x)
Y.append(y)
else:
if len(X)>0 and len(Y)>0:
Z.append([X,Y])
del X,Y
X = []
Y = []
for i in range(0, len(Z)):
plt.plot(Z[i][0],Z[i][1])
plt.grid(True)
plt.show()