0

I have been attempting to optimize my matplotlib animation which involves adding a rectangle to the plot with each iteration. Since the animation function updates the entire set of patches with each iteration it is very slow for large sets of patches.

According to several sources, this process can be sped up by using a PatchCollection instead of looping over patches. This is my attempt:

from matplotlib import pyplot
from matplotlib import patches
from matplotlib import animation
from matplotlib.collections import PatchCollection

fig = pyplot.figure()

coords = [[1,0],[0,1],[0,0]] #arbitrary set of coordinates

ax = pyplot.axes(xlim=(0, len(coords)), ylim=(0, len(coords)))
ax.set_aspect('equal')


patchList = list()

for coord in coords:
    patch = patches.Rectangle(coord, 1, 1, color="white")
    ax.add_patch(patch)
    patch.set_visible = True
    patchList.append(patch)

rectangles = PatchCollection(patchList, animated=True)
ax.add_collection(rectangles)


black = []
white = ["white"]*len(patchList)

def animate(i):
    black.append("black")
    white.pop()

    colors = black + white
    print(colors)

    rectangles.set_facecolors(colors)
    print("%.2f%%..."%(100*float(i+1)/len(coords)))

    return rectangles,

anim = animation.FuncAnimation(fig, animate,
                               # init_func=init,
                               frames=len(coords)-1,
                               interval=1,
                               blit=True,
                               repeat = False)

pyplot.show()

The animation never updates. It is frozen on an empty plot.

I would be open to other means of optimizing this. The current solution seems pretty verbose for just adding a single rectangle each frame.

Ryan
  • 203
  • 2
  • 9

1 Answers1

2

I'm not sure I fully understand your code. Is the idea that switching the facecolor of a rectangle from white to black is "adding" that rectangle to the plot?

It took me a bit of time to figure out why your code was not working, but it turns out to be a silly mistake. The problem is with the line ax.add_patch(patch). You are actually adding two sets of rectangles on your plot, once as a single Rectangle instance, and the second as part of the PatchCollection. It seems like the Rectangles stay on top of the collection, and so you're just not seeing the animation. Just commenting the offending line (ax.add_patch(patch)) seems to fix the problem.

Also, FuncAnimation(..., interval=) is expressed in ms, so you might want to increase the value to something larger (100ms, or 1000ms i.e. 1s) to see the animation.

Here how I would write the code (loosely inspired by this question on SO):

Nx = 30
Ny = 20
size = 0.5

colors = ['w']*Nx*Ny

fig, ax = plt.subplots()

rects = []
for i in range(Nx):
    for j in range(Ny):
        rect = plt.Rectangle([i - size / 2, j - size / 2], size, size)
        rects.append(rect)

collection = PatchCollection(rects, animated=True)

ax.add_collection(collection)
ax.autoscale_view(True)

def init():
    # sets all the patches in the collection to white
    collection.set_facecolor(colors)
    return collection,

def animate(i):
    colors[i] = 'k'
    collection.set_facecolors(colors)
    return collection,

ani = FuncAnimation(fig, init_func=init, func=animate, frames=Nx*Ny,
        interval=1e2, blit=True)

enter image description here

Diziet Asahi
  • 38,379
  • 7
  • 60
  • 75
  • Thanks, this solves my problem. That extra add_patch line was a vestige of the previous method I used, which did not use the PatchCollections class, so I just forgot to remove it. – Ryan Aug 31 '17 at 21:26
  • Previously, the actual time to update the plot was the limiting factor. I am animating 500-1000 squares so I wanted it to run as fast as possible. 20ms would be a 50fps animation, which is good for my purposes. Also I agree it makes more sense to substitute the i-th value in the color list. If you were to write your own animation of this style, would you use PatchCollections and edit the color array, or would you do it another way? – Ryan Aug 31 '17 at 21:36
  • I don't have any experience dealing with lots of elements to animate. But as far as I know, as long as you can accept the limits of working with a `Collection` object, it will always be faster than drawing the individual patches – Diziet Asahi Sep 01 '17 at 07:26