from IPython.display import HTML
from matplotlib.pyplot import subplots
from matplotlib.animation import FuncAnimation
from matplotlib.dates import AutoDateLocator, ConciseDateFormatter, date2num
from numpy import linspace
from pandas import to_datetime
rc('animation', embed_limit=2**25)
rc('axes.spines', top=False, right=False)
fig, ax = subplots(figsize=(14, 10), gridspec_kw={'right': .7})
artists = {
'start': ax.plot([], [], color='tab:blue', lw=2)[0],
'critical': ax.plot([], [], color='tab:red', lw=3, zorder=9)[0],
'finish': ax.plot([], [], color='tab:blue', lw=2)[0],
'title': ax.set_title('', size='x-large'),
'annot_max': ax.annotate(
'We were making so much money here',
(date2num(to_datetime('2001-09-01')), s.loc['2001-09-01']),
xytext=(50, 0), textcoords='offset points', bbox={'facecolor': 'white', 'alpha': 0, 'edgecolor': 'none'},
alpha=0, ha='left', va='center', arrowprops={'facecolor': 'black', 'shrink': .05, 'alpha': 0}
),
'annot_right': ax.annotate(
'I bet you’re looking\nover here now', (s.index[-1], s.iloc[-1]),
alpha=0, ha='left', va='bottom'
),
'highlight': ax.axvspan(
*date2num(['2000-11-01', '2001-09-01']), ymin=0, ymax=0,
alpha=.5, color='yellow'
)
}
def frame_gen():
yield 'pause', 'A Story About a Line'
for d in s.loc[:'2000-11-01'].copy().expanding():
yield 'start', d
for _ in range(20):
yield 'pause', 'A Pause... For Dramatic Effect!'
for d in s.loc['2000-11-01':'2001-09-01'].expanding():
yield 'critical', d
for d in s.loc['2001-09-01':].expanding():
yield 'finish', d
for alpha in linspace(0, 1, 20):
yield 'annot_right', {'alpha': alpha}
for alpha in linspace(0, 1, 20)[::-1]:
yield 'annot_right', {'alpha': alpha}
yield 'annot_right', {
'text': 'Let’s go back to our roots\nto figure out what caused\nour exponential growth',
'va': 'top'
}
for alpha in linspace(0, 1, 20):
yield 'annot_right', {'alpha': alpha}
for _ in range(60):
yield 'pause', None
yield 'annot_right', {'alpha': 0}
for height in linspace(0, 1, 50):
yield 'highlight', {'height': height}
yield 'pause', 'We started using animated visualizations to expain our data!'
yield 'annot_max', {'text': {'alpha': 1}, '_bbox_patch': {'alpha': 1}, 'arrow_patch': {'alpha': 1}}
for _ in range(60):
yield 'pause', None
yield 'pause', 'Thanks for Watching This Matplotlib Animation!'
def update(args):
match args:
case ('pause', title):
if title is not None:
artists['title'].set_text(title)
return [artists['title']]
case ('start', data):
line = artists['start']
artists['title'].set_text('At the Beginning, We Were Barely Scraping by...')
line.set_data(data.index, data.to_numpy())
return [line, artists['title']]
case ('critical', data):
line = artists['critical']
artists['title'].set_text('Then, WE HIT GOLD!')
line.set_data(data.index, data.to_numpy())
return [line, artists['title']]
case ('finish', data):
line = artists['finish']
artists['title'].set_text('But the Economy Began Worsening')
line.set_data(data.index, data.to_numpy())
return [line, artists['title']]
case ('annot_right', d):
artists['annot_right'].set(**d)
return [artists['annot_right']]
case ('highlight', d):
if 'height' in d:
xy = artists['highlight'].get_xy()
height = d.pop('height')
xy[:, 1] = [0, height, height, 0, 0]
d['xy'] = xy
artists['highlight'].set(**d)
return [artists['highlight']]
case ('annot_max', d):
artists['annot_max'].set(**d.get('text', {}))
artists['annot_max']._bbox_patch.set(**d.get('_bbox_patch', {}))
artists['annot_max'].arrow_patch.set(**d.get('arrow_patch', {}))
return [artists['annot_max']]
case _:
raise ValueError('bad match case')
ax.update_datalim([(date2num(s.index.min()), s.min()), (date2num(s.index.max()), s.max())])
ax.autoscale_view()
ax.margins(x=0)
locator = AutoDateLocator()
formatter = ConciseDateFormatter(
locator, zero_formats=['', '%b\n%Y', '%b', '%b-%d', '%H:%M', '%H:%M']
)
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(formatter)
animation = FuncAnimation(
fig, update, frames=frame_gen, blit=True,
save_count=1 + len(s)+ 20 + 20 + 1 + 20 + 20 + 1 + 60 + 20 + 1 + 50 + 60,
interval=50
)
ax.yaxis.set_major_formatter(lambda x, pos: f'{x/1_000_000:g}M')
ax.set_ylabel('Cumulative Revenue')
html = animation.to_jshtml(default_mode='once')
HTML(html)