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()
Related
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
I want to display history of CPU and RAM usage in Jupyter Notebook in real time. Something like this (Process Explorer in Windows):
I don't interactivity so I use matplotlib in inline mode. I run a separate background thread and try to update two different plots from there. It works well with one plot but the second one blinks and has duplicates.
Here's a minimal example (also I pickle/unpickle plot so I can initialize it only once and reuse later).
Installed packages:
ipykernel 5.1.3
ipywidgets 7.5.1
jupyter 1.0.0
jupyter-core 4.6.1
matplotlib 3.1.1
notebook 6.0.0
import pickle
import threading
import time
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
def init_history_plot():
"""
Create plot template (dump)
Returns: pickled str
"""
# plt.figure(figsize=(15, 1.2))
# ax = plt.axes()
fig, ax = plt.subplots(figsize=(15, 1.2))
# Y axis min-max
ax.set_ylim(0, 100)
# ax.get_xaxis().set_visible(False)
ax.grid(axis='y')
# right tick labels https://stackoverflow.com/a/13369977
ax.yaxis.tick_right()
# hide ticks https://stackoverflow.com/a/33707647
ax.yaxis.set_ticks_position('none')
# borders https://stackoverflow.com/a/27361819
# for i in ax.spines.values(): # 'left', 'right', 'top', 'bottom'
# ax.spines[i].set_visible(False)
# https://stackoverflow.com/questions/18603959/borderless-matplotlib-plots
ax.set_frame_on(False)
dat = pickle.dumps(fig)
plt.close()
return dat
def load_figure(dump):
"""
Load Figure from dump
Returns: (Figure, Axes)
"""
# https://github.com/ipython/ipykernel/issues/231
import ipykernel.pylab.backend_inline as back_inline
import matplotlib.backends.backend_agg as back_agg
back_inline.new_figure_manager_given_figure = back_agg.new_figure_manager_given_figure
figure = pickle.loads(dump)
# https://github.com/matplotlib/matplotlib/issues/17627/
figure._cachedRenderer = None
return figure, figure.axes[0]
template_fig = init_history_plot()
btn_start = widgets.ToggleButton(description="Start thread")
plt1_parent = widgets.Output()
plt2_parent = widgets.Output()
_interface = widgets.VBox(children=[btn_start, plt1_parent, plt2_parent])
def worker():
while btn_start.value:
with plt1_parent:
plt1_parent.clear_output(wait=True)
fig, ax = load_figure(template_fig)
dat = np.random.normal(scale=20, size=50) + 50
ax.plot(dat, color='green')
plt.show()
# THE FOLLOWING BLOCK BLINKS
with plt2_parent:
plt2_parent.clear_output(wait=True)
fig, ax = load_figure(template_fig)
dat = np.random.normal(scale=20, size=50) + 50
ax.plot(dat, color='red')
plt.show()
############################
time.sleep(1)
def start_thread(_):
if btn_start.value:
thread = threading.Thread(target=worker)
thread.start()
btn_start.observe(start_thread, 'value')
_interface
I think your code is really cool, and so I greedily stole it because I also want to have plots that live-update in a Jupyter notebook without blocking the kernel.
Anyway, the weird flickering was bothering me as well. I tried switching from threading to asyncio, simply because I am more familiar with asyncio than threading, and that actually seems to have solved the problem! Though I have no idea why.
Try:
import pickle
import asyncio
import time
import ipywidgets as widgets
import matplotlib.pyplot as plt
import numpy as np
def init_history_plot():
"""
Create plot template (dump)
Returns: pickled str
"""
# plt.figure(figsize=(15, 1.2))
# ax = plt.axes()
fig, ax = plt.subplots(figsize=(15, 1.2))
# Y axis min-max
ax.set_ylim(0, 100)
# ax.get_xaxis().set_visible(False)
ax.grid(axis='y')
# right tick labels https://stackoverflow.com/a/13369977
ax.yaxis.tick_right()
# hide ticks https://stackoverflow.com/a/33707647
ax.yaxis.set_ticks_position('none')
# borders https://stackoverflow.com/a/27361819
# for i in ax.spines.values(): # 'left', 'right', 'top', 'bottom'
# ax.spines[i].set_visible(False)
# https://stackoverflow.com/questions/18603959/borderless-matplotlib-plots
ax.set_frame_on(False)
dat = pickle.dumps(fig)
plt.close()
return dat
def load_figure(dump):
"""
Load Figure from dump
Returns: (Figure, Axes)
"""
# https://github.com/ipython/ipykernel/issues/231
import ipykernel.pylab.backend_inline as back_inline
import matplotlib.backends.backend_agg as back_agg
back_inline.new_figure_manager_given_figure = back_agg.new_figure_manager_given_figure
figure = pickle.loads(dump)
# https://github.com/matplotlib/matplotlib/issues/17627/
figure._cachedRenderer = None
return figure, figure.axes[0]
template_fig = init_history_plot()
btn_start = widgets.ToggleButton(description="Start thread")
plt1_parent = widgets.Output()
plt2_parent = widgets.Output()
_interface = widgets.VBox(children=[btn_start, plt1_parent, plt2_parent])
async def worker():
while btn_start.value:
with plt1_parent:
plt1_parent.clear_output(wait=True)
fig, ax = load_figure(template_fig)
dat = np.random.normal(scale=20, size=50) + 50
ax.plot(dat, color='green')
plt.show()
with plt2_parent:
plt2_parent.clear_output(wait=True)
fig, ax = load_figure(template_fig)
dat = np.random.normal(scale=20, size=50) + 50
ax.plot(dat, color='red')
plt.show()
await asyncio.sleep(1)
def start_thread(_):
if btn_start.value:
task = asyncio.create_task(worker())
btn_start.observe(start_thread, 'value')
_interface
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 have data stored in stock_data in JSON format (which can be any arbitrary data). I want to plot 4 axes, and WHEN there is new data, update the graph (through animate I am assuming).
I only want this to occur when using INTRADAY data (as you can see I have an if intraday check at the bottom). I am pulling this intraday data from an API. This API updates every minute or so, and only during certain hours. I don't mind if it doesn't update instantly, but ideally within a 1 minute period of new data.
I have tried pulling new data and comparing it to the old DF (as you can see at the end of the code) and putting it in a while True: loop, however the graph fails to render. I have tried simply putting the entire function in a loop and rendering the graph every time - this not only takes ages to render, but if I am zoomed in on the graph, it completely resets it. I figure this is a problem with redrawing?
Finally, I am unsure what to put in the animation.FuncAnimation() either.. I have excluded ax3 and ax4 because they'll act the same as ax2 for demonstration purposes. Help is much appreciated.
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib.dates as mdates
import matplotlib.animation as animation
from mpl_finance import candlestick_ohlc
import numpy as np
## CANDLESTICK GRAPH ##
def candlestick(symbol, MA1 = 20, MA2 = 200):
try:
## arbitrary colors ##
candle_upcol = '#cccccc'
candle_downcol = '#cccccc'
fill_col = '#cccccc'
bg_col = '#cccccc'
spine_col = '#cccccc'
## load stocks ##
stock_data = pd.DataFrame.from_dict(json.load(open('db/AAPL.txt')), orient = 'index', dtype = np.float64)
stock_data = stock_data.values
## BEGIN PLOTTING ##
start_point = len(stock_data[max(MA1, MA2)-1:])
fig = plt.figure(facecolor=bg_col)
#set grids
ax1 = plt.subplot2grid((8,4), (1,0), rowspan = 5, colspan = 4, facecolor = bg_col)
ax2 = plt.subplot2grid((8,4), (7,0), rowspan = 1, colspan = 4, sharex = ax1, facecolor= bg_col)
ax3 = plt.subplot2grid((8,4), (0,0), rowspan = 1, colspan = 4, sharex = ax1, facecolor = bg_col)
ax4 = plt.subplot2grid((8,4), (6,0), rowspan = 1, colspan = 4, sharex = ax1, facecolor = bg_col)
#PRICE plot (AX1)
candlestick_ohlc(ax1, stock_data[-start_point:,0:5], width = 0.6, colorup = candle_upcol, colordown = candle_downcol)
ax1.xaxis.set_major_locator(mticker.MaxNLocator(10))
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax1.grid(True)
plt.setp(ax1.get_xticklabels(), visible=False) #remove x ticks
#MOVING AVERAGES plot (AX1)
if MA1 != 0:
av1 = moving_average(stock_data[:,4], MA1) #using close prices
label_ma1 = '{MA} SMA'.format(MA = str(MA1))
ax1.plot(stock_data[-start_point:,0], av1[-start_point:], label = label_ma1, color = '#aec6cf', linewidth = .8)
if MA2 != 0:
av2 = moving_average(stock_data[:,4], MA2) #using close prices
label_ma2 = '{MA} SMA'.format(MA = str(MA2))
ax1.plot(stock_data[-start_point:,0], av2[-start_point:], label = label_ma2, color = '#ffb347', linewidth = .8)
if MA1 != 0 or MA2 != 0:
ax1.text(0, 1, 'MA ({MA1}, {MA2})'.format(MA1 = str(MA1), MA2 = str(MA2)), va = 'top', ha = 'left', color = 'w', transform = ax1.transAxes, alpha = 0.5, fontweight = 'bold')
#VOLUME plot (AX2)
volume_min = 0 #stock_data[:,5].min()
ax2.plot(stock_data[-start_point:,0], stock_data[-start_point:,5], '#00ffe8', linewidth = .8)
ax2.fill_between(stock_data[-start_point:,0], volume_min, stock_data[-start_point:,5], facecolor = fill_col, alpha = 0.5)
ax2.axes.yaxis.set_ticklabels([]) #remove y ticks
ax2.text(0, 1, 'VOLUME', va = 'top', ha = 'left', color = 'w', transform = ax2.transAxes, alpha = 0.5, fontweight = 'bold')
#RSI plot (AX3)
#similar to VOL
#MACD plot (AX4)
#similar to VOL
#SHARED plot (ALL AX)
for all_ax in (ax1, ax2''', ax3, ax4'''):
plt.setp(all_ax.spines.values(), color=spine_col)
all_ax.tick_params(axis='both', colors = 'w')
all_ax.yaxis.label.set_color("w")
all_ax.yaxis.tick_right()
all_ax.xaxis.set_tick_params(labelsize=9)
all_ax.yaxis.set_tick_params(labelsize=9)
#ENTIRE plot
plt.subplots_adjust(hspace = 0)
fig.autofmt_xdate()
fig.suptitle('{STOCK}'.format(STOCK = symbol), color = 'w', fontweight='bold', alpha = 0.75)
print('Drawing graph.')
if data_type != 'Intraday':
print('Graphing complete.')
else:
#this will be replaced by an API fetch function at some point, this is just for testing if animation works.. needs a sleep function? and while True loop..?
new_stock_data = pd.DataFrame.from_dict(json.load(open('db/AAPL_new.txt')), orient = 'index', dtype = np.float64)
new_stock_data = new_stock_data.values
if (new_stock_data[-1] == stock_data[-1]).all() == False:
stock_data = np.vstack([stock_data, new_stock_data[-1]])
#ani = animation.FuncAnimation(fig, '''???''', interval = 10000) #blit=True?
plt.show()
except:
print('Failed main loop.')
A FuncAnimation will draw (or blit) repeatedly at a rate given by the interval. In case that is not desired, one could use a timer instead. The timer calls a function that will, depending on some condition either do nothing, or update the plot with new data. This way you make sure to only draw the canvas when new data is available (i.e. condition is true).
import datetime
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
t = []
x = []
line, = ax.plot_date(t,x, ls="-")
def update():
now = datetime.datetime.now()
if np.random.rand() > 0.9:
t.append(now)
x.append(np.random.randn())
line.set_data(t,x)
ax.relim()
ax.autoscale_view()
fig.canvas.draw_idle()
message = "new data drawn"
else:
message = "no new data"
print(now.time(), message)
timer = fig.canvas.new_timer(interval=200)
timer.add_callback(update)
timer.start()
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.