If I understand correctly, the file, output.txt
, contains a sequence of pickled dicts.
Each dict is of the form
{ i: self.points[i].point_location }
Each call to update
should read 100 (new) dicts from this file.
We can do this by making a generator function, get_data
, which yields items from the file one at a time. Then define
data = get_data(path)
outside of update
and pass it to update
as an argument (using FuncAnimation
's fargs
parameter.) Inside of update
, the loop
for point, coordinate in itertools.islice(data, 100):
iterates through 100 items from data
. Since data
is an iterator, itertools.islice
will yield 100 new items from data
each time it is called.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import pickle
import itertools as IT
def init():
ax.set_xlim(0, 20)
ax.set_ylim(0, 20)
return (pathcol,)
def pickleLoader(f):
"""
Return generator which yields unpickled objects from file object f
# Based on https://stackoverflow.com/a/18675864/190597 (Darko Veberic)
"""
try:
while True:
yield pickle.load(f)
except EOFError:
pass
def get_data(path):
with open(path, "rb") as f:
for dct in pickleLoader(f):
yield from dct.items()
def update(i, data, pathcol, texts, title, num_points):
title.set_text("Generation {}".format(i))
xdata, ydata, color = [], [], []
for point, coordinate in IT.islice(data, num_points):
texti = texts[point]
x, y = coordinate.cpu().numpy()
xdata.append(x)
ydata.append(y)
color.append(point)
texti.set_position((x, y))
color = np.array(color, dtype="float64")
color /= num_points
pathcol.set_color = color
pathcol.set_offsets(np.column_stack([xdata, ydata]))
return [pathcol, title] + texts
class Coord:
# just to make the code runnable
def __init__(self, coord):
self.coord = coord
def cpu(self):
return Cpu(self.coord)
class Cpu:
# just to make the code runnable
def __init__(self, coord):
self.coord = coord
def numpy(self):
return self.coord
def make_data(path, num_frames=1000, num_points=100):
# just to make the code runnable
with open(path, "wb") as output:
for frame in range(num_frames):
for i in range(num_points):
points = {i: Coord(20 * np.random.random((2,)))}
pickle.dump(points, output)
def _blit_draw(self, artists, bg_cache):
# https://stackoverflow.com/a/17562747/190597 (tacaswell)
# Handles blitted drawing, which renders only the artists given instead
# of the entire figure.
updated_ax = []
for a in artists:
# If we haven't cached the background for this axes object, do
# so now. This might not always be reliable, but it's an attempt
# to automate the process.
if a.axes not in bg_cache:
# bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.bbox)
# change here
bg_cache[a.axes] = a.figure.canvas.copy_from_bbox(a.axes.figure.bbox)
a.axes.draw_artist(a)
updated_ax.append(a.axes)
# After rendering all the needed artists, blit each axes individually.
for ax in set(updated_ax):
# and here
# ax.figure.canvas.blit(ax.bbox)
ax.figure.canvas.blit(ax.figure.bbox)
# MONKEY PATCH!!
matplotlib.animation.Animation._blit_draw = _blit_draw
num_points = 100
num_frames = 1000
fig, ax = plt.subplots()
pathcol = ax.scatter(
[0] * num_points, [0] * num_points, c=np.linspace(0, 1, num_points), s=100
)
title = ax.text(
0.5,
1.05,
"",
transform=ax.transAxes,
horizontalalignment="center",
fontsize=15,
animated=True,
)
texts = []
for i in range(num_points):
t = ax.text(0, 0, str(i), fontsize=10, animated=True)
texts.append(t)
path = "/tmp/output.txt"
make_data(path, num_frames, num_points) # just to make the code runnable
data = get_data(path)
ani = FuncAnimation(
fig,
update,
range(1, num_frames + 1),
init_func=init,
blit=True,
fargs=(data, pathcol, texts, title, num_points),
interval=1000, # decrease to speed up the animation
repeat=False,
)
plt.show()