Matplotlib animation patch initialization and rotation. How to do it right? - python

What are the right ways to initialize and rotate patches in animations? The attached program initializes and rotates patches in an animation, but my solutions are kind of a kludge.
To keep the initialized patches from showing up as static patches, I set alpha=0, but this means that I have to set alpha=1 every time I call the animate function, when I should only have to set this once. I could put in an "if i==0;" statement before the set_alpha(1) call, but that seems inelegant.
To rotate the patches, I use "mag1._angle = i" (see lines 37 & 39) which uses Rectangle's internal attribute _angle. You're not really supposed to do this but I don't see an alternative easy solution. What do the Pythonistas say?
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
fig = plt.figure()
ax = plt.axes(xlim=(-1, 1), ylim=(-1, 1), aspect=1)
ax.set_xticks([])
ax.set_yticks([])
r_in, r_out = 0.25, 0.35
circ_outer = plt.Circle((0, 0), radius=r_out, ec='blue', fc='white', lw=3)
circ_inner = plt.Circle((0, 0), radius=r_in, ec='blue', fc='white', lw=3)
ax.add_patch(circ_outer)
ax.add_patch(circ_inner)
r, w, h = 0.5, 0.07, 0.4
x0, y0 = r, 0.5*h
mag1 = plt.Rectangle((-x0,-y0), width= w, height= h, angle=0, fc='black', alpha=0)
mag2 = plt.Rectangle(( x0, y0), width=-w, height=-h, angle=0, fc='black', alpha=0)
line1, = ax.plot([], [], 'b', zorder=1)
line2, = ax.plot([], [], 'b', zorder=1)
def init():
line1.set_data([], [])
line2.set_data([], [])
ax.add_patch(mag1)
ax.add_patch(mag2)
return mag1, mag2, line1, line2,
def animate(i):
mag1.set_alpha(1)
mag2.set_alpha(1)
thetaMAG = np.radians(i)
x = x0*np.cos(thetaMAG) - y0*np.sin(thetaMAG)
y = x0*np.sin(thetaMAG) + y0*np.cos(thetaMAG)
mag1.xy = (-x, -y)
mag1._angle = i
mag2.xy = (x, y)
mag2._angle = i
j = float(i)/3.0
thetaROT = np.radians(j)
sn = r_in*np.sin(thetaROT)
cs = r_in*np.cos(thetaROT)
line1.set_data([-cs, cs], [-sn, sn])
line2.set_data([sn, -sn], [-cs, cs])
return mag1, mag2, line1, line2,
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=1080,
interval=25, blit=True)
plt.show()

Related

Matplot lib animation not working as expected

I have 4 variables like this:
# generate 4 random variables from the random, gamma, exponential, and uniform distributions
x1 = np.random.normal(-2.5, 1, 10000)
x2 = np.random.gamma(2, 1.5, 10000)
x3 = np.random.exponential(2, 10000)+7
x4 = np.random.uniform(14,20, 10000)
And I need to create one figure with 4 subplots.
so I tried this:
plt.figure(figsize=(9,3))
plt.subplot(1,4,1)
plt.hist(x1, normed=True, bins=20, alpha=0.5)
plt.subplot(1,4,2)
plt.hist(x2, normed=True, bins=20, alpha=0.5)
plt.subplot(1,4,3)
plt.hist(x3, normed=True, bins=20, alpha=0.5)
plt.subplot(1,4,4)
plt.hist(x4, normed=True, bins=20, alpha=0.5)
plt.axis([-7,21,0,0.6])
And I got this result
Now I want to create an animation on the subplots, so I did the following (trying one subplot only)
import matplotlib.animation as animation
def update(curr):
if curr == n:
a.event_source.stop()
plt.cla()
plt.figure(figsize=(9,3))
plt.subplot(1,4,1)
plt.hist(x1, normed=True, bins=20, alpha=0.5)
plt.axis([-7,21,0,0.6])
plt.gca().set_title('Sample')
plt.gca().set_ylabel('Frequency')
plt.gca().set_xlabel('Value')
plt.annotate('n = {}'.format(curr), [3.27])
fig = plt.figure()
a = animation.FuncAnimation(fig, update, interval=100)
However the end result is empty, nothing is shown.
Any idea?
I re-structured your code in order to plot the animation of the 4 subplots.
Without any specific indication on what you want to see changing between one frame and the next, I assume the number of sample drawn from each distribution is inscreasing in each frame by 10.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def update(curr):
N = 10*curr
x1 = np.random.normal(-2.5, 1, N)
x2 = np.random.gamma(2, 1.5, N)
x3 = np.random.exponential(2, N) + 7
x4 = np.random.uniform(14, 20, N)
ax[0].cla()
ax[0].hist(x1, bins = 20, alpha = 0.5, color = 'blue', edgecolor = 'blue')
ax[0].set_title('Normal')
ax[0].set_ylabel('Frequency')
ax[0].set_xlabel('Value')
ax[0].set_xlim(-6, 1)
ax[1].cla()
ax[1].hist(x2, bins = 20, alpha = 0.5, color = 'blue', edgecolor = 'blue')
ax[1].set_title('Gamma')
ax[1].set_ylabel('Frequency')
ax[1].set_xlabel('Value')
ax[1].set_xlim(0, 12)
ax[2].cla()
ax[2].hist(x3, bins = 20, alpha = 0.5, color = 'blue', edgecolor = 'blue')
ax[2].set_title('Exponential')
ax[2].set_ylabel('Frequency')
ax[2].set_xlabel('Value')
ax[2].set_xlim(7, 25)
ax[3].cla()
ax[3].hist(x4, bins = 20, alpha = 0.5, color = 'blue', edgecolor = 'blue')
ax[3].set_title('Uniform')
ax[3].set_ylabel('Frequency')
ax[3].set_xlabel('Value')
ax[3].set_xlim(14, 20)
ax[0].set_ylim(0, 250)
fig.suptitle(f'Number of samples: {N}')
plt.tight_layout()
fig, ax = plt.subplots(1, 4, figsize = (9, 3), sharey = 'all')
a = FuncAnimation(fig, update, interval = 100, frames = 81)
plt.show()

Matplotlib Animation : Graph can't appear

Recently I have managed to do the animation. But now I can't make the tangent line went as I want (not even displayed yet). The formula of the tangent line is y=(2/r)(sqrt(1-((r^2)/4))-1)x +r. The formula is obtained from 2 circles equation (C1 and C2). C1(blue) : x^2+y^2=r^2, and C2(green) : (x-1)^2+y^2=1. My goal is to obtain this kind of animation as and my current animation goes like this .
How should the code looks like when the animation looks like the reference one (the first one)? Any comments and answers would be very helpful for me as a beginner, I appreciate it, Thank you.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots(1)
line, = ax.plot([], [], lw=2)
line2, = ax.plot([], [], lw=2)
ax.set_xlim(-5,5)
ax.set_ylim(-5,5)
# theta goes from 0 to 2pi
theta = np.linspace(0, 2*np.pi, 100)
# the radius of the circle
r = np.sqrt(1)
r2 = np.sqrt(4)
# compute x1 and x2
x1 = 1+r*np.cos(theta)
y1 = r*np.sin(theta)
x2 = r2*np.cos(theta)
y2 = r2*np.sin(theta)
# Move left y-axis and bottim x-axis to centre, passing through (0,0)
ax.spines['left'].set_position('center')
ax.spines['bottom'].set_position('center')
# Eliminate upper and right axes
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
# Show ticks in the left and lower axes only
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
def init():
line.set_data([], [])
return line,
def init2():
line.set_data([], [])
return line2,
def animate(i):
x2 = np.sqrt(i)*np.cos(theta)
y2 = np.sqrt(i)*np.sin(theta)
line.set_data(x2, y2)
return line,
def animate2(i):
x3 = np.linspace(0,r2**2,100)
y3 = ((2/r2)*(np.sqrt(1-(r2**2)/4)-1)*x3)+r2
line.set_data(x3, y3)
return line,
# create the figure
ax.plot(x1,y1)
ax.set_aspect(1)
plt.grid()
anim = animation.FuncAnimation(fig, animate, init_func=init,
interval=1000, blit=False,\
frames=np.hstack([range(0),
range(4)[::-1]]))
anim2 = animation.FuncAnimation(fig, animate2, init_func=init2,
interval=1000, blit=False)
plt.show()
f = r"D:/UNPAR/Semester 2/Pemrograman Komputer/Project/SK.gif"
writergif = animation.PillowWriter(fps=30)
anim.save(f, writer=writergif)
The animation functions need to be combined into one. We will combine them into an initialization function and an animation function.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots(1)
line, = ax.plot([], [], lw=2, color='green')
line2, = ax.plot([], [], lw=2, color='red')
ax.set_xlim(-5,5)
ax.set_ylim(-5,5)
# theta goes from 0 to 2pi
theta = np.linspace(0, 2*np.pi, 100)
# the radius of the circle
r = np.sqrt(1)
r2 = np.sqrt(4)
# compute x1 and x2
x1 = 1+r*np.cos(theta)
y1 = r*np.sin(theta)
x2 = r2*np.cos(theta)
y2 = r2*np.sin(theta)
# create the figure
ax.plot(x1,y1)
ax.set_aspect(1)
plt.grid()
def init():
line.set_data([], [])
line2.set_data([], [])
return line,line2
def animate(i):
x2 = np.sqrt(i)*np.cos(theta)
y2 = np.sqrt(i)*np.sin(theta)
print(max(y2))
line.set_data(x2, y2)
x3 = [0, 4-(0.1*i)]
y3 = [max(y2), 0]
line2.set_data(x3, y3)
return line,line2
anim = animation.FuncAnimation(fig, animate, init_func=init, interval=1000, blit=False,frames=np.arange(10,0,-1))
plt.show()

How to pause matplotlib animation for some seconds(without using any mouse clicks)?

I have created a script that animates two scatter points and a line in between them. Here is the gif:
And here is the script used for animation:
from a import get_points
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
fig, ax = plt.subplots(figsize=(12,8))
ax.set(xlim=(0,104), ylim=(0,68))
x_start, y_start = (50, 35)
x_end, y_end = (90, 45)
x_1, y_1 = get_points(x_start, y_start, x_end, y_end, 0.55)
x_2, y_2 = get_points(x_end, y_end, x_start, y_start, 0.55)
x = np.linspace(x_1, x_2, 20)
y = np.linspace(y_1, y_2, 20)
sc_1 = ax.scatter([], [], color="green", zorder=4)
line, = ax.plot([], [], color="crimson", zorder=4)
sc_2 = ax.scatter([], [], color="gold", zorder=4)
title = ax.text(50, 65, "", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5}, ha="center")
def animate(i):
## plot scatter point
sc_1.set_offsets([x_start, y_start])
## plot line
line.set_data(x[:i], y[:i])
## plot scatter point
if i == len(x):
sc_2.set_offsets([x_end, y_end])
return sc_1, line, sc_2, title,
ani = animation.FuncAnimation(
fig=fig, func=animate, interval=50, blit=True)
plt.show()
What I want is: to pause the animation for 2 seconds when the first scatter point shows up then animate the line and when the line animation is complete pause animation for 2 more seconds and after that display the scatter point.
What should I change in my code to get the required animation?
Answer
You can do it with plt.pause().
I simplified a bit your code in order to use it without the unknown a module (and its get_points() function).
Code
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots(figsize=(12,8))
ax.set(xlim=(0,104), ylim=(0,68))
x_start, y_start = (50, 35)
x_end, y_end = (90, 45)
N = 20
x = np.linspace(x_start, x_end, N)
y = np.linspace(y_start, y_end, N)
sc_1 = ax.scatter([], [], color="green", zorder=4)
line, = ax.plot([], [], color="crimson", zorder=4)
sc_2 = ax.scatter([], [], color="gold", zorder=4)
def animate(i):
sc_1.set_offsets([x_start, y_start])
if i == 1:
plt.pause(2)
line.set_data(x[:i], y[:i])
if i == len(x):
plt.pause(2)
sc_2.set_offsets([x_end, y_end])
return sc_1, line, sc_2,
ani = animation.FuncAnimation(fig=fig, func=animate, interval=50, blit=True)
plt.show()

How to do dynamic matplotlib plotting with a fixed pandas dataframe?

I have a dataframe called benchmark_returns and strategy_returns. Both have the same timespan. I want to find a way to plot the datapoints in a nice animation style so that it shows all the points loading in gradually. I am aware that there is a matplotlib.animation.FuncAnimation(), however this typically is only used for a real-time updating of csv files etc but in my case I know all the data I want to use.
I have also tried using the crude plt.pause(0.01) method, however this drastically slows down as the number of points get plotted.
Here is my code so far
x = benchmark_returns.index
y = benchmark_returns['Crypto 30']
y2 = benchmark_returns['Dow Jones 30']
y3 = benchmark_returns['NASDAQ']
y4 = benchmark_returns['S&P 500']
fig, ax = plt.subplots()
line, = ax.plot(x, y, color='k')
line2, = ax.plot(x, y2, color = 'b')
line3, = ax.plot(x, y3, color = 'r')
line4, = ax.plot(x, y4, color = 'g')
def update(num, x, y, y2, y3, y4, line):
line.set_data(x[:num], y[:num])
line2.set_data(x[:num], y2[:num])
line3.set_data(x[:num], y3[:num])
line4.set_data(x[:num], y4[:num])
return line, line2, line3, line4,
ani = animation.FuncAnimation(fig, update, fargs=[x, y, y2, y3, y4, line],
interval = 1, blit = True)
plt.show()
You could try matplotlib.animation.ArtistAnimation. It operates similar to FuncAnimation in that you can specify the frame interval, looping behavior, etc, but all the plotting is done at once, before the animation step. Here is an example
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from matplotlib.animation import ArtistAnimation
n = 150
x = np.linspace(0, np.pi*4, n)
df = pd.DataFrame({'cos(x)' : np.cos(x),
'sin(x)' : np.sin(x),
'tan(x)' : np.tan(x),
'sin(cos(x))' : np.sin(np.cos(x))})
fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(10,10))
lines = []
artists = [[]]
for ax, col in zip(axs.flatten(), df.columns.values):
lines.append(ax.plot(df[col])[0])
artists.append(lines.copy())
anim = ArtistAnimation(fig, artists, interval=500, repeat_delay=1000)
The drawback here is that each artist is either drawn or not, i.e. you can't draw only part of a Line2D object without doing clipping. If this is not compatible with your use case then you can try using FuncAnimation with blit=True and chunking the data to be plotted each time as well as using set_data() instead of clearing and redrawing on every iteration. An example of this using the same data from above:
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from matplotlib.animation import FuncAnimation
n = 500
nf = 100
x = np.linspace(0, np.pi*4, n)
df = pd.DataFrame({'cos(x)' : np.cos(x),
'sin(x)' : np.sin(x),
'tan(x)' : np.tan(x),
'sin(cos(x))' : np.sin(np.cos(x))})
fig, axs = plt.subplots(2, 2, figsize=(5,5), dpi=50)
lines = []
for ax, col in zip(axs.flatten(), df.columns):
lines.append(ax.plot([], lw=0.5)[0])
ax.set_xlim(x[0] - x[-1]*0.05, x[-1]*1.05)
ax.set_ylim([min(df[col].values)*1.05, max(df[col].values)*1.05])
ax.tick_params(labelbottom=False, bottom=False, left=False, labelleft=False)
plt.subplots_adjust(hspace=0, wspace=0, left=0.02, right=0.98, bottom=0.02, top=0.98)
plt.margins(1, 1)
c = int(n / nf)
def animate(i):
if (i != nf - 1):
for line, col in zip(lines, df.columns):
line.set_data(x[:(i+1)*c], df[col].values[:(i+1)*c])
else:
for line, col in zip(lines, df.columns):
line.set_data(x, df[col].values)
return lines
anim = FuncAnimation(fig, animate, interval=2000/nf, frames=nf, blit=True)
Edit
In response to the comments, here is the implementation of a chunking scheme using the updated code in the question:
x = benchmark_returns.index
y = benchmark_returns['Crypto 30']
y2 = benchmark_returns['Dow Jones 30']
y3 = benchmark_returns['NASDAQ']
y4 = benchmark_returns['S&P 500']
line, = ax.plot(x, y, color='k')
line2, = ax.plot(x, y2, color = 'b')
line3, = ax.plot(x, y3, color = 'r')
line4, = ax.plot(x, y4, color = 'g')
n = len(x) # Total number of rows
c = 50 # Chunk size
def update(num):
end = num * c if num * c < n else n - 1
line.set_data(x[:end], y[:end])
line2.set_data(x[:end], y2[:end])
line3.set_data(x[:end], y3[:end])
line4.set_data(x[:end], y4[:end])
return line, line2, line3, line4,
ani = animation.FuncAnimation(fig, update, interval = c, blit = True)
plt.show()
or, more succinctly
cols = benchmark_returns.columns.values
# or, for only a subset of the columns
# cols = ['Crypto 30', 'Dow Jones 30', 'NASDAQ', 'S&P 500']
colors = ['k', 'b', 'r', 'g']
lines = []
for c, col in zip(cols, colors):
lines.append(ax.plot(benchmark_returns.index, benchmark_returns[col].values, c=c)[0])
n = len(benchmark_returns.index)
c = 50 # Chunk size
def update(num):
end = num * c if num * c < n else n - 1
for line, col in zip(lines, cols):
line.set_data(benchmark_returns.index, benchmark_returns[col].values[:end])
return lines
anim = animation.FuncAnimation(fig, update, interval = c, blit=True)
plt.show()
and if you need it to stop updating after a certain time simply set the frames argument and repeat=False in FuncAnimation().
You can just update the data into the line element like so:
fig = plt.figure()
ax = fig.add_subplot(111)
liner, = ax.plot()
plt.ion()
plt.show()
for i in range(len(benchmark_returns.values)):
liner.set_ydata(benchmark_returns['Crypto 30'][:i])
liner.set_xdata(benchmark_returns.index[:i])
plt.pause(0.01)

Animation with matplotlib where points are dynamically added to a graph

I've written a simple code which generates random points (x0, y0) between certain values using a while loop. After the coordinates of each point are set, that point is drawn in an empty graph which is showed at the end of the while loop.
However, I would like to set up an animation with matplotlib which would allow me to see the initial graph and the points progressively added to it as the code is calculating them. I've looked for some examples but the ones I found are mainly concerned with waves and so on and I guess I need a slightly different approach.
This is the basic code:
from numpy import *
from pylab import *
import random
figure(figsize=(8,6), dpi=150)
x = np.linspace(-1, 4.5, 250)
h=5
a=0.5
b=4
ylim(-0.5,5.5)
xlim(-1,5.0)
i= 0
while i< 500:
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
scatter(x0, y0, 10, color="red")
i = i + 1
show()
Thanks for your help!
EDIT: ANIMATION CODE
import numpy as np
import matplotlib.pyplot as plt
from pylab import *
import matplotlib.animation as animation
import random
fig = plt.figure(figsize=(8,6), dpi=150)
x = np.linspace(-2, 4.5, 250)
h=4
a=1
b=3
hlines(y=h, xmin=1, xmax=3, linewidth=1.5)
vlines(x=a, ymin=0, ymax=4, linewidth=1.5)
vlines(x=b, ymin=0, ymax=4, linewidth=1.5)
ylim(-2.5,10.5)
xlim(-2.5,4.5)
grid()
def data_gen():
i = 0
while i< 1:
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
i = i + 1
yield x0, y0
line, = plot([], [], linestyle='none', marker='o', color='r')
ax = gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
xdata, ydata = [], []
def run(data):
x0,y0 = data
xdata.append(x0)
ydata.append(y0)
line.set_data(xdata, ydata)
return line,
ani = animation.FuncAnimation(fig, run, data_gen, blit=True, interval=0.5,
repeat=False)
plt.show()
I do not know if this is exactly what you are looking for; in any case, you can generate random points inside the run function and there plot them. You do not need neither blit = True nor clear the axis from one frame to another.
Here is my code:
from pylab import *
from matplotlib.animation import FuncAnimation
import random
fig = plt.figure(figsize=(8,6), dpi=150)
x = np.linspace(-2, 4.5, 250)
h=4
a=1
b=3
hlines(y=h, xmin=a, xmax=b, linewidth=1.5)
vlines(x=a, ymin=0, ymax=h, linewidth=1.5)
vlines(x=b, ymin=0, ymax=h, linewidth=1.5)
ylim(-2.5,10.5)
xlim(-2.5,4.5)
grid()
ax = gca()
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.xaxis.set_ticks_position('bottom')
ax.spines['bottom'].set_position(('data',0))
ax.yaxis.set_ticks_position('left')
ax.spines['left'].set_position(('data',0))
def run(i):
R1 = random.random()
R2 = random.random()
x0 = (b - a)*R1 + a
y0 = h*R2
ax.scatter(x0, y0, 10, color='red')
ani = FuncAnimation(fig = fig, func = run, frames = 500, interval = 10, repeat = False)
plt.show()
which produces this animation:
(I cut this animation to 100 points in order to get a lighter file, less than 2 MB; the code above produces an animation wiht 500 points)

Categories