Matplotlib: animation saved with trace - python-3.x

In the following Python code, I am trying to make an animation of a 2 rotating vectors around the origin. I am using matplotlib 3.2.1 and Python 3.8.2 on Ubuntu 20.04.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
r = 2.0
def circle(phi):
return np.array([r*np.cos(phi), r*np.sin(phi)])
fig, ax = plt.subplots(figsize=(10,6))
ax.axis([-3.5*r,3.5*r,-2.5*r,2.5*r])
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
# set equal aspect
ax.set_aspect("equal")
point, = ax.plot(0, r, marker="o")
traj = plt.Circle((0,0), r, fill=False, color='black')
ax.add_artist(traj) # draw the circular trajectory
def update(phi):
x, y = circle(phi)
point.set_data([x], [y])
er_vec = np.array([0.5*x, 0.5*y])
eθ_vec = np.array([-0.5*y, 0.5*x])
er_arr = plt.arrow(x, y, dx=er_vec[0], dy=er_vec[1], head_width=0.1, head_length=0.2, color='gray')
eθ_arr = plt.arrow(x, y, dx=eθ_vec[0], dy=eθ_vec[1], head_width=0.1, head_length=0.2, color='grey')
annot_er = plt.text(1.7*x, 1.7*y, r'$\mathbf{e}_r$', fontsize=11)
annot_eθ = plt.text(1.1*(x-0.5*y), 1.1*(y+0.5*x), r'$\mathbf{e}_\theta$', fontsize=11)
ax.add_artist(er_arr)
ax.add_artist(eθ_arr)
ax.add_artist(annot_er)
ax.add_artist(annot_eθ)
return point, er_arr, eθ_arr, annot_er, annot_eθ
anim = FuncAnimation(fig, update, interval=10, blit=True, repeat=False, frames=np.linspace(0, 2.0*np.pi, 360, endpoint=False))
plt.show()
The code above runs smoothly and without any issues.
This is a screenshot of the animation:
However, when I try to save the animation to an mp4 video:
anim.save('anim-issue.mp4', writer='ffmpeg')
the animation in the video appears with traces which, something like this screenshot:
Could someone help me fix that issue with the video animation?
I appreciate your help.
Edit 1: According to this answer this is due to blit=True. But that doesn't solve the issue here, since the arrows have no set_position method.
Edit 2: I found another related question with the same issue I described above but I don't know how to adapt my code to make it work as expected in both cases (plt.show, anim.save).

Updating the position of an arrow would be quite tricky.
The easiest solution is indeed to create new arrows at each frame, but you have to make sure you remove the previous arrows first
full code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
r = 2.0
def circle(phi):
return np.array([r*np.cos(phi), r*np.sin(phi)])
fig, ax = plt.subplots(figsize=(10,6))
ax.axis([-3.5*r,3.5*r,-2.5*r,2.5*r])
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
# set equal aspect
ax.set_aspect("equal")
point, = ax.plot(0, r, marker="o")
traj = plt.Circle((0,0), r, fill=False, color='black')
ax.add_artist(traj) # draw the circular trajectory
er_arr = None
eθ_arr = None
annot_er = None
annot_eθ = None
def init():
global er_arr, eθ_arr, annot_er, annot_eθ
x,y = 0,0
er_vec = np.array([0.5*x, 0.5*y])
eθ_vec = np.array([-0.5*y, 0.5*x])
er_arr = plt.arrow(x, y, dx=er_vec[0], dy=er_vec[1], head_width=0.1, head_length=0.2, color='gray')
eθ_arr = plt.arrow(x, y, dx=eθ_vec[0], dy=eθ_vec[1], head_width=0.1, head_length=0.2, color='grey')
annot_er = plt.text(1.7*x, 1.7*y, r'$\mathbf{e}_r$', fontsize=11)
annot_eθ = plt.text(1.1*(x-0.5*y), 1.1*(y+0.5*x), r'$\mathbf{e}_\theta$', fontsize=11)
return er_arr, eθ_arr, annot_er, annot_eθ
def update(phi):
global er_arr, eθ_arr, annot_er, annot_eθ
x, y = circle(phi)
point.set_data([x], [y])
er_vec = np.array([0.5*x, 0.5*y])
eθ_vec = np.array([-0.5*y, 0.5*x])
#remove previous artists
er_arr.remove()
eθ_arr.remove()
er_arr = plt.arrow(x, y, dx=er_vec[0], dy=er_vec[1], head_width=0.1, head_length=0.2, color='gray')
eθ_arr = plt.arrow(x, y, dx=eθ_vec[0], dy=eθ_vec[1], head_width=0.1, head_length=0.2, color='grey')
annot_er.set_position((1.7*x, 1.7*y))
annot_eθ.set_position((1.1*(x-0.5*y), 1.1*(y+0.5*x)))
return point, er_arr, eθ_arr, annot_er, annot_eθ
anim = FuncAnimation(fig, update, init_func=init, interval=10, blit=True, repeat=False, frames=np.linspace(0, 2.0*np.pi, 360, endpoint=False))
plt.show()

Related

Matplotlib Scatter plot interactivity not working

Until this morning I was able to display labels information when hovering the dots on a scatter plot.
Now, if I run the following code it does not display any error but the interactivity is not working and it looks like mplconnect or mlpcursors are completely ignored.
I've tried the same code under windows and Fedora.
Not understanding what's going on.
from matplotlib.pyplot import figure, show
import numpy as npy
from numpy.random import rand
x, y, c, s = rand(4, 100)
def onpick3(event):
ind = event.ind
print('onpick3 scatter:', ind, npy.take(x, ind), npy.take(y, ind))
fig = figure()
ax1 = fig.add_subplot(111)
col = ax1.scatter(x, y, 100*s, c, picker=True)
#fig.savefig('pscoll.eps')
fig.canvas.mpl_connect('pick_event', onpick3)
show()
Or
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
x = np.random.rand(15)
y = np.random.rand(15)
names = np.array(list("ABCDEFGHIJKLMNO"))
c = np.random.randint(1,5,size=15)
norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn
fig,ax = plt.subplots()
sc = plt.scatter(x,y,c=c, s=100, cmap=cmap, norm=norm)
annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
bbox=dict(boxstyle="round", fc="w"),
arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)
def update_annot(ind):
pos = sc.get_offsets()[ind["ind"][0]]
annot.xy = pos
text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))),
" ".join([names[n] for n in ind["ind"]]))
annot.set_text(text)
annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
annot.get_bbox_patch().set_alpha(0.4)
def hover(event):
vis = annot.get_visible()
if event.inaxes == ax:
cont, ind = sc.contains(event)
if cont:
update_annot(ind)
annot.set_visible(True)
fig.canvas.draw_idle()
else:
if vis:
annot.set_visible(False)
fig.canvas.draw_idle()
fig.canvas.mpl_connect("motion_notify_event", hover)
plt.show()
This is not my code, I've copied and pasted it from a website but the behavior is the same.
Plotly express solves the problem
import plotly.express as px
alpha = data[data['Ticker']==focus].V1
gamma = data[data['Ticker']==focus].V2
fig = px.scatter(data, x='V1', y='V2', color=Colors.Market_Cap, hover_data=["Ticker"] )
fig.add_shape(type="circle",
xref="x", yref="y",
x0=int(alpha-3), y0=int(gamma-3), x1=int(alpha+3), y1=int(gamma+3),
line_color="LightSeaGreen",
)
fig.show()

Python plots graph into button instead of figure

I'm a total beginner to python (started some days ago).
This little program is supposed to draw sinus waves, and the buttons at the bottom should increase / decrease the frequency of the plotted sinus. But my program plots the sinus into the button, for whatever reason. Where is the mistake? I've tried so much already... Thanks in advance ❤
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
class Function:
def __init__(self, amplitude, frequency):
self.amplitude = amplitude
self.frequency = frequency
self.time = np.arange(0.0, 1.0, 0.001)
self.signal= amplitude*np.sin(2*np.pi*frequency*self.time)
def status(self):
print("The frequency is: ", self.frequency)
print("The amplitude is: ", self.amplitude)
def setFrequency(self, frequency):
self.frequency = frequency
def setAmplitue(self, amplitude):
self.amplitude = amplitude
def show(self):
plt.plot(self.time, self.signal, linewidth = 2)
plt.ylabel("Sinus")
plt.xlabel("x")
pass
func = Function(1, 20)
func.show()
def IncreaseFrequency(event):
global func
if (func.frequency < 100):
func.setFrequency(func.frequency + 10)
else:
func.setFrequency(10)
func.show()
plt.draw()
pass
def LowerFrequency(event):
global func
if (func.frequency > 10):
func.setFrequency(func.frequency - 10)
else:
func.setFrequency(100)
func.show()
plt.draw()
pass
buttonIncrSize = plt.axes([0.7, 0.01, 0.1, 0.05])
buttonLowerSize = plt.axes([0.81, 0.01, 0.1, 0.05])
buttonIncrFreq = Button(buttonIncrSize, 'Next')
buttonLowerFreq = Button(buttonLowerSize, 'Previous')
buttonIncrFreq.on_clicked(IncreaseFrequency)
buttonLowerFreq.on_clicked(LowerFrequency)
plt.show()
First what looks kind of weird is that after lowering or increasing your frequency, you never update the self.signal, which should change if your frequency changes.
Secondly, from what I could see on this link, plt.plot returns an Line2D object which you can use to set the y data on your plot using line2D.set_ydata(new_ydata).
After changing the y data, just update the plot using plt.draw() and that should work.
EDIT: Indeed I didn't have access to my PC but ran this now and didn't work. After some search:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import matplotlib
class Function:
def __init__(self, amplitude, frequency):
self.amplitude = amplitude
self.frequency = frequency
self.time = np.arange(0.0, 1.0, 0.001)
self.signal = amplitude * np.sin(2 * np.pi * frequency * self.time)
def status(self):
print("The frequency is: ", self.frequency)
print("The amplitude is: ", self.amplitude)
def setFrequency(self, frequency):
self.frequency = frequency
self.signal = self.amplitude * np.sin(2 * np.pi * frequency * self.time)
def setAmplitude(self, amplitude):
self.amplitude = amplitude
self.signal = self.amplitude * np.sin(2 * np.pi * frequency * self.time)
def show(self):
plt.cla()
plt.plot(self.time,self.signal,lw=2)
plt.draw()
plt.ylabel("Sinus")
plt.xlabel("x")
def IncreaseFrequency(event):
global func
if (func.frequency < 100):
func.setFrequency(func.frequency + 10)
else:
func.setFrequency(10)
func.show()
def LowerFrequency(event):
global func
if (func.frequency > 10):
func.setFrequency(func.frequency - 10)
else:
func.setFrequency(100)
func.show()
fig, ax = plt.subplots()
func = Function(1, 20)
func.show()
buttonIncrSize = plt.axes([0.7, 0.01, 0.1, 0.05])
buttonLowerSize = plt.axes([0.81, 0.01, 0.1, 0.05])
buttonIncrFreq = Button(buttonIncrSize, 'Next')
buttonLowerFreq = Button(buttonLowerSize, 'Previous')
buttonIncrFreq.on_clicked(IncreaseFrequency)
buttonLowerFreq.on_clicked(LowerFrequency)
plt.sca(fig.axes[0])
plt.show()
Main change being the plt.sca(fig.axes[0]) which allows to select the current axis for plt. When you run plt.axes() it sets the current axes of plt to the button, hence subsequently plotting your graph in that button.
Also added plt.cla() which clears the current plotting area.

How to connect matplotlib cursor mouse_move object with slider value?

I have a figure where 2 axhlines move with mouse movement. I want to put a slider at the bottom where it will change the range of y-axis values covered by these axhlines.
enter image description here
I tried the following code. Problem is that the value of the slider changes but the mouse event object does not update.
Thanks
%matplotlib notebook
import pandas as pd
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from matplotlib.widgets import Slider
np.random.seed(12345)
df = pd.DataFrame([np.random.normal(32000,200000,3650),
np.random.normal(43000,100000,3650),
np.random.normal(43500,140000,3650),
np.random.normal(48000,70000,3650)],
index=[1992,1993,1994,1995])
df=df.T
data=df.describe().T
data['error']=df.sem()
data['error_range']=df.sem()*1.96
fig, ax = plt.subplots()
def plot_bar(x,y,error,title,alpha_level=0.7):
ax.bar(x,y, yerr=error,
align='center', alpha=alpha_level,
error_kw=dict(ecolor='black', elinewidth=1, capsize=5, capthick=1))
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.set_title(title)
ax.xaxis.set_major_locator(ticker.FixedLocator(data.index))
ax.yaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
ax.set_ylim([-5000,55000])
ax.set_xlim([1990.5,1995.5])
ax.spines['bottom'].set_position(('data',0))
ax.spines['left'].set_position(('data',1991.4))
plt.tight_layout()
return (ax, ax.get_children()[1:5])
ax, barlist=plot_bar(x=data.index,y=data['mean'],error=data['error_range'],title='Even Harder Option', alpha_level=0.6)
fig.subplots_adjust(bottom=0.1)
axcolor = 'lightgoldenrodyellow'
range_slider = plt.axes([0.2, 0.05, 0.65, 0.03], facecolor=axcolor)
slider = Slider(range_slider, 'Range', 0, 55000, valinit=10000, valstep=100)
def update(val):
slider.val = slider.val
slider.on_changed(update)
class Cursor(object):
_df=None
_bl=None
def __init__(self, ax,data_F, bars, slider):
#global slider
self._df=data_F
self._bl=bars
self.ax = ax
self.lx1 = ax.axhline(color='b')
self.lx2 = ax.axhline(color='b')
self.text1 = ax.text(1990.55, y, '%d' %45,bbox=dict(fc='white',ec='k'), fontsize='x-small')
self.text2 = ax.text(1990.55, y, '%d' %45,bbox=dict(fc='white',ec='k'), fontsize='x-small')
self._sl = slider.val
def mouse_move(self, event):
if not event.inaxes:
return
x, y = event.xdata, event.ydata
r = self._sl
y1 , y2 = y+r/2 , y-r/2
#self.lx1.set_ydata(y)
self.lx1.set_ydata(y+r/2)
self.lx2.set_ydata(y-r/2)
for i in range(4):
#shade = cmap(norm((data['mean'.values[i]-event.ydata)/df_std.values[i]))
prob1=stats.norm.cdf(y1,self._df['mean'].values[i],self._df['error'].values[i])
prob2=stats.norm.cdf(y2,self._df['mean'].values[i],self._df['error'].values[i])
shade = cmap(prob1-prob2)
self._bl[i].set_color(shade)
self.text1.set_text('%d' %y1)
self.text1.set_position((1990.55, y1))
self.text2.set_text('%d' %y2)
self.text2.set_position((1990.55, y2))
plt.draw()
cursor = Cursor(ax, data,barlist, slider)
#plt.connect('range_change', cursor.update)
plt.connect('motion_notify_event', cursor.mouse_move)

Tkinter Not Showing Animated Matplotlib

I want to show this matplotlib real-time graph in tkinter GUI
Real-time Graph
import tkinter as tk
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
root= tk.Tk()
style.use('fivethirtyeight')
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
graph = open('data.txt','r').read()
lines = graph.split('\n')
xs = []
ys = []
zs = []
for line in lines:
if len(line) > 1:
x, y, z = line.split(',')
xs.append(float(x))
ys.append(float(y))
zs.append(float(z))
ax1.clear()
ax1.plot(xs, ys, zs)
anim = animation.FuncAnimation(fig, animate, interval=1000)
app = (fig, root)
root.mainloop()
I tried the code above, but the GUI isn't show anything. What can I do to show that real-time graph?
Sorry, my bad. I updated my code in this line:
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
With this line:
fig = plt.figure(figsize=(5,4), dpi=100)
ax1 = fig.add_subplot(111)
line2 = FigureCanvasTkAgg(fig, root)
line2.get_tk_widget().pack(side=tk.LEFT, fill=tk.BOTH)
I got my answer now.

How to add label and title to matplotlib animation

I am not able to add label and title to matplotlib animation.
Please find my code below
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
import time
%matplotlib inline
style.use("ggplot")
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def animate(i):
pullData = open("twitter-out.txt","r").read()
lines = pullData.split('\n')
xar = []
yar = []
x = 0
y = 0
for l in lines[-200:]:
x += 1
if "pos" in l:
y += 1
elif "neg" in l:
y -= 1
xar.append(x)
yar.append(y)
ax1.clear()
ax1.plot(xar,yar)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.show()

Resources