I am trying to make an animation with continue rotating an image, but the output video file has empty content(Only axis left), how to fix it?
import math
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import scipy.misc
from scipy import ndimage
my_image="img.png"
out_file="myvideo.mp4"
class UpdateDist:
def __init__(self, ax):
self.ax = ax
self.img = mpimg.imread(my_image)
self.ax.imshow(self.img)
self.degree = 1
def __call__(self, i):
rotated_img = ndimage.rotate(img, self.degree*10)
self.ax.imshow(rotated_img)
self.degree += 1
return self.ax,
plt.axis(False)
plt.grid(False)
fig, ax = plt.subplots()
ud = UpdateDist(ax)
anim = FuncAnimation(fig, ud, frames=100, interval=10, blit=True)
plt.show()
ani.save(out_file, fps=30, extra_args=['-vcodec', 'libx264'])
I applied some edits to your code:
replaced self.degree with i: i increases by 1 in each iteration, no need for another counter
moved ax.grid(False) and ax.axis(False) (and added ax.clear()) within __call__ method, in order to use them in each frame
removed blit parameter from FuncAnimation
replaced .mp4 output file format with .gif
used imagemagik as writer
Let me know if this code achieves your goal or if you need any further modifications.
Complete Code
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy import ndimage
import numpy as np
my_image='img.png'
out_file='myvideo.gif'
class UpdateDist:
def __init__(self, ax, rotational_speed):
self.ax = ax
self.img = plt.imread(my_image)
self.rotational_speed = rotational_speed
def __call__(self, i):
rotated_img = ndimage.rotate(self.img, self.rotational_speed*i, reshape=False)
self.ax.clear()
self.ax.grid(False)
self.ax.axis(False)
self.ax.imshow((rotated_img*255).astype(np.uint8))
return self.ax,
fig, ax = plt.subplots()
ud = UpdateDist(ax = ax, rotational_speed = 1)
anim = FuncAnimation(fig, ud, frames = 91, interval = 1)
anim.save(filename = out_file, writer = 'pillow', fps = 30)
Animation
Related
I am using matplotlib.animation right now I got some good result.
I was looking to add some stuff to the chart but couldn't find how.
adding "buy"/ "sell" arrow when button clicked let say '1' - for buy , '2' for sell.
simple label/legend that will show current live values (open,high,low,close , volume)
This is my code below:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import pandas as pd
import mplfinance as mpf
import matplotlib.animation as animation
idf = pd.read_csv('aapl.csv',index_col=0,parse_dates=True)
#df = idf.loc['2011-07-01':'2012-06-30',:]
pkwargs=dict(type='candle',mav=(20,200))
fig, axes = mpf.plot(idf.iloc[0:20],returnfig=True,volume=True,
figsize=(11,8),panel_ratios=(3,1),
title='\n\nS&P 500 ETF',**pkwargs,style='starsandstripes')
ax1 = axes[0]
ax2 = axes[2]
ax1.spines['top'].set_visible(True)
ax1.grid(which='major', alpha=0.1)
ax2.grid(which='major', alpha=0.1)
#fig = plt.figure()
def run_animation():
ani_running = True
def onClick(event):
nonlocal ani_running
if ani_running:
ani.event_source.stop()
ani_running = False
else:
ani.event_source.start()
ani_running = True
def animate(ival):
if (20+ival) > len(idf):
print('no more data to plot')
ani.event_source.interval *= 3
if ani.event_source.interval > 12000:
exit()
return
#print("here")
#mpf.plot(idf,addplot=apd)
data = idf.iloc[100+ival:(250+ival)]
print(idf.iloc[ival+250])
ax1.clear()
ax2.clear()
mpf.plot(data,ax=ax1,volume=ax2,**pkwargs,style='yahoo')
fig.canvas.mpl_connect('button_press_event', onClick)
ani = animation.FuncAnimation(fig, animate, interval=240)
run_animation()
mpf.show()
I am not clear on what exactly you want when you say you want to add "buy"/ "sell" arrow, but I can answer your question on creating a legend.
I think the easiest thing is to build a custom legend by specifying the colors and labels. The labels will change as your animation proceeds, so you will need to update the legend in your animate function after you clear both axes and plot the latest data - I assume that the row of the DataFrame that you are printing is the one you want to display in the legend:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import pandas as pd
import mplfinance as mpf
import matplotlib.patches as mpatches
idf = pd.read_csv('https://raw.githubusercontent.com/matplotlib/sample_data/master/aapl.csv',index_col=0,parse_dates=True)
#df = idf.loc['2011-07-01':'2012-06-30',:]
pkwargs=dict(type='candle',mav=(20,200))
fig, axes = mpf.plot(idf.iloc[0:20],returnfig=True,volume=True,
figsize=(11,8),panel_ratios=(3,1),
title='\n\nS&P 500 ETF',**pkwargs,style='starsandstripes')
ax1 = axes[0]
ax2 = axes[2]
## define the colors and labels
colors = ["red","green","red","green"]
labels = ["Open", "High", "Low", "Close"]
#fig = plt.figure()
def run_animation():
ani_running = True
def onClick(event):
nonlocal ani_running
if ani_running:
ani.event_source.stop()
ani_running = False
else:
ani.event_source.start()
ani_running = True
def animate(ival):
if (20+ival) > len(idf):
print('no more data to plot')
ani.event_source.interval *= 3
if ani.event_source.interval > 12000:
exit()
return
#print("here")
#mpf.plot(idf,addplot=apd)
data = idf.iloc[100+ival:(250+ival)]
print(idf.iloc[ival+250])
## what to display in legend
values = idf.iloc[ival+250][labels].to_list()
legend_labels = [f"{l}: {str(v)}" for l,v in zip(labels,values)]
handles = [mpatches.Patch(color=c, label=ll) for c,ll in zip(colors, legend_labels)]
ax1.clear()
ax2.clear()
mpf.plot(data,ax=ax1,volume=ax2,**pkwargs,style='yahoo')
## add legend after plotting
ax1.legend(handles=handles, loc=2)
fig.canvas.mpl_connect('button_press_event', onClick)
ani = animation.FuncAnimation(fig, animate, interval=240)
run_animation()
mpf.show()
I'm trying to align the chart to the top and left side of the window, eleminating all the whitespace there. Also the labels on the x axis are cut-off for some reason, even though I'm using tight_layout(). On top of this I have trouble removing the scientific notation from the y axis. (I want to display the original str or float held in the variable, If I use plt.yaxis.set_major_formatter(ScalarFormatter(useOffset=False)) I get an attribute arror: 'matplotlib.pyplot' has no attribute 'yaxis'.
This is what it looks like:
from binance.client import Client
import time, os, csv, datetime
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import matplotlib as mpl
from matplotlib.ticker import ScalarFormatter
client = Client(apikey, apisecret)
mpl.rcParams['toolbar'] = 'None'
fig = plt.figure(figsize=(4,3))
plt.style.use('ggplot')
plt.rcParams['ytick.right'] = plt.rcParams['ytick.labelright'] = True
plt.rcParams['ytick.left'] = plt.rcParams['ytick.labelleft'] = False
x_vars = []
y_vars = []
def animate(i):
global x_vars
global y_vars
if len(x_vars) > 30:
x_vars = x_vars[-30:]
y_vars = y_vars[-30:]
else:
pass
current_time = client.get_server_time()
current_price = client.get_symbol_ticker(symbol="XRPBTC")
trstime = current_time["serverTime"] / 1000
time = datetime.datetime.fromtimestamp(int(trstime)).strftime('%M:%S')
x_vars.append(str(time))
y_vars.append(float(current_price["price"]))
plt.cla()
plt.plot(x_vars, y_vars)
plt.xticks(rotation = 45)
ani = animation.FuncAnimation(fig, animate, interval=500)
plt.tight_layout()
plt.show()
Set the y-axis to the right.:
ax.yaxis.tick_right()
ax.yaxis.set_label_position("right")
Don't display offset:
plt.setp(ax.get_yaxis().get_offset_text(), visible=False)
Dealing with missing ticks (disabling tight display):
# plt.tight_layout()
To confirm this, I created an animation with the binance API and checked it. Since the essence of this question is not the animation, I think you will get faster answers from more people if you focus your question only on the graph part.
full code:
import datetime
import json
import requests
import matplotlib.pyplot as plt
import matplotlib as mpl
from matplotlib.animation import FuncAnimation
url = "https://api.binance.com/api/v3/ticker/price?symbol=XRPBTC"
mpl.rcParams['toolbar'] = 'None'
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
ax.yaxis.tick_right()
ax.yaxis.set_label_position("right")
plt.setp(ax.get_yaxis().get_offset_text(), visible=False)
plt.style.use('ggplot')
x_vars = []
y_vars = []
def animate(i):
global x_vars
global y_vars
if len(x_vars) > 30:
x_vars = x_vars[-30:]
y_vars = y_vars[-30:]
else:
pass
current_time = 0
current_price = 0
r = requests.get(url)
response_dict = r.json()
current_price = float(response_dict['price'])
dt_now = datetime.datetime.now()
ttime = '{:02}:{:02}'.format(dt_now.minute, dt_now.second)
x_vars.append(ttime)
y_vars.append(current_price)
ax.cla()
ax.plot(x_vars, y_vars)
ax.set_xticklabels(x_vars, rotation=45)
ani = FuncAnimation(fig, animate, frames=30, interval=500, repeat=False)
# plt.tight_layout()
plt.show()
I'm not quite getting how to create a class for animating data. Here is the gist:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.arange(100).reshape((100, 1))
y = np.random.randn(100, 1)
xy = np.hstack((x, y))
class PlotData:
def __init__(self):
fig, ax = plt.subplots()
fig.set_size_inches((11, 9))
self.fig = fig
self.ax = ax
self.ln0, = ax.plot([], [])
def init(self):
self.ln0.set_data([], [])
return(self.ln0, )
def update(self, frame_no):
data = xy[0:frame_no + 1]
self.ln0.set_data(data[:, 0], data[:, 1])
return(self.ln0, )
if __name__ == '__main__':
my_plot = PlotData()
anim = animation.FuncAnimation(my_plot.fig, my_plot.update,
init_func=my_plot.init, blit=True,
frames=99, interval=50)
plt.show()
This only produces the init method output but not the update, so ends up a blank plot with no animation. What is going on?
For me your code works perfectly fine. The only problem is that most of the data are outside of the plotting limits. If you adjust your plot limits like this:
class PlotData:
def __init__(self):
fig, ax = plt.subplots(figsize = (11,9))
self.fig = fig
self.ax = ax
self.ax.set_xlim([0,100])
self.ax.set_ylim([-3,3])
self.ln0, = ax.plot([], [])
The line is animated just fine. If you want that the x- and y-limits are adjusted automatically, see this question on how to do it. However, if I recall correctly, this will only work properly with blit=False.
I've modified the strip chart example to make an animated moving line. However every time I move the strip chart over, the first 2 line segments get replotted. As shown here on my screen shot:
However if I hit the "save" button on the Figure and save it the extra line segment isn't there. (Note, these are 2 different test runs with different random data, but on my screen the two extra segments are always present when the animation is running)
It also doesn't plot immediately in the animation. The first 2 segments will plot and then it will show up on the plot. These values aren't in the data lists for tdata or ydata. Here's the sample code:
import numpy as np
from matplotlib.lines import Line2D
import matplotlib.pyplot as plt
import matplotlib.animation as animation
class Scope:
def __init__(self, ax, maxt=5, dt=1):
self.ax = ax
self.dt = dt
self.maxt = maxt
self.tdata = []
self.tdata.append(0)
self.ydata = []
self.ydata.append(20)
self.line = Line2D(self.tdata, self.ydata)
self.ax.add_line(self.line)
self.ax.set_ylim(21, 24)
self.ax.set_xlim(0, self.maxt)
def update(self, y):
lastt = self.tdata[-1]
if lastt > self.tdata[0] + self.maxt: # reset the arrays
self.tdata = [self.tdata[-1]]
self.ydata = [self.ydata[-1]]
self.ax.set_xlim(self.tdata[0], self.tdata[0] + self.maxt)
self.ax.figure.canvas.draw()
t = self.tdata[-1] + self.dt
self.tdata.append(t)
self.ydata.append(y)
self.line.set_data(self.tdata, self.ydata)
return self.line,
def fetchPrice():
yield np.random.rand(1) + 23
fig, ax = plt.subplots()
scope = Scope(ax)
ani = animation.FuncAnimation(fig, scope.update, fetchPrice, interval=1000,
blit=True)
plt.show()
Let me know if you have any ideas about what is going on and how to fix it. Thanks.
It's caused by wrong indentation. Dedent the line in update (See <---- marked line in the following code):
def update(self, y):
lastt = self.tdata[-1]
if lastt > self.tdata[0] + self.maxt: # reset the arrays
self.tdata = [self.tdata[-1]]
self.ydata = [self.ydata[-1]]
self.ax.set_xlim(self.tdata[0], self.tdata[0] + self.maxt)
self.ax.figure.canvas.draw() # <----
t = self.tdata[-1] + self.dt
self.tdata.append(t)
self.ydata.append(y)
self.line.set_data(self.tdata, self.ydata)
return self.line,
I am a newbie into wx python. The following is the code to plot live graph from a text file which can be updated live. Can anybody please help me to embed this code into a wx frame. I desperately need it for my project.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
fig= plt.figure()
ax1=fig.add_subplot(1,1,1)
def animate(i):
pullData= open('C:/test/e.txt','r').read()
dataArray= pullData.split('\n')
xar=[]
yar=[]
for eachLine in dataArray:
if len(eachLine)>1:
x,y= eachLine.split(',')
xar.append(int(x))
yar.append(int(y))
ax1.clear()
ax1.plot(xar,yar)
ani= animation.FuncAnimation(fig,animate, interval=1000)
plt.show()
Here I'll give you an example but you need to change the plotting part for your needs:
import wx
import numpy as np
import matplotlib.figure as mfigure
import matplotlib.animation as manim
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
class MyFrame(wx.Frame):
def __init__(self):
super(MyFrame,self).__init__(None, wx.ID_ANY, size=(800, 600))
self.fig = mfigure.Figure()
self.ax = self.fig.add_subplot(111)
self.canv = FigureCanvasWxAgg(self, wx.ID_ANY, self.fig)
self.values = []
self.animator = manim.FuncAnimation(self.fig,self.anim, interval=1000)
def anim(self,i):
if i%10 == 0:
self.values = []
else:
self.values.append(np.random.rand())
self.ax.clear()
self.ax.set_xlim([0,10])
self.ax.set_ylim([0,1])
return self.ax.plot(np.arange(1,i%10+1),self.values,'d-')
wxa = wx.PySimpleApp()
w = MyFrame()
w.Show(True)
wxa.MainLoop()