6

I want to make an animated barchart in Python and save this animation in mp4 format. My problem is that the frames in the saved video overlay, although I use "blit=True" to tell the animation that only the things that change from frame to frame are drawn. Surprisingly, this problem does not occur in the built-in preview of Python. Here is a minimal that reflects my situation:

import matplotlib.pyplot as plt
from matplotlib import animation

def barlist(n): #That's the list of bars I want to display
    C=[]
    for k in range(1,6):
        C.append(1/float(n*k))
    return C

fig=plt.figure()

n=100 #Number of frames

def animate(i):
    x=range(1,6)
    y=barlist(i+1)
    return plt.bar(x,y)

anim=animation.FuncAnimation(fig,animate,repeat=False,blit=True,frames=n,
                             interval=50)
anim.save('barchart_animated_'+str(n)+'.mp4')
plt.show()

I must admit that I'm not pretty sure what I should do to remove this flaw. The only example I know of where the bars do not overlay in the frames is here (more exactly, I'm referring to the code of the first answer of the following link):

Dynamically updating a bar plot in matplotlib

It seems that I somehow have to tell the animation how it should set the height of each bar at each frame with the set_height-method. But as I said, I don't really know what's wrong in the above example. Thanks for any help!

Martin

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
Martin Monath
  • 117
  • 1
  • 1
  • 7
  • 1
    Why exactly do you want to use blit when saving the animation? Apart from that, it's unclear what the problem is. It seems you are unhappy about the result of your program. But that could have many different reasons. Can you provide a clear problem description and a related actual question? – ImportanceOfBeingErnest Feb 05 '17 at 20:00
  • I've edited my question now. Does this help you? – Martin Monath Feb 09 '17 at 17:18

1 Answers1

18

The problem you have here is that you create a new barplot in every iteration of the animation. They will one by one be added to the plot, but since their height is shrinking over time, it may look as though only the first bar is present.

There are two ways to overcome this. First option is to clear the axes before plotting a new bar plot. This however will rescale the axis limits, which should then be constantly set to the same value.

The other option is to manipulate the one and only bar plot in the axes and adapt it's height for every frame. This is shown in the code below.

import matplotlib.pyplot as plt
from matplotlib import animation

def barlist(n): 
    return [1/float(n*k) for k in range(1,6)]

fig=plt.figure()

n=100 #Number of frames
x=range(1,6)
barcollection = plt.bar(x,barlist(1))

def animate(i):
    y=barlist(i+1)
    for i, b in enumerate(barcollection):
        b.set_height(y[i])

anim=animation.FuncAnimation(fig,animate,repeat=False,blit=False,frames=n,
                             interval=100)

anim.save('mymovie.mp4',writer=animation.FFMpegWriter(fps=10))
plt.show()

Answers to the questions from the comments:

Blotting is a technique where all the parts of the figure which do not change are stored as a background. Then for each animated frame, only the changing parts are redrawn. This avoids the background to be redrawn from scratch and thus allows for much faster animations. Blitting will only affect the on-screen animation, because saving the animation to a file is not performed in real-time (and doesn't need to anyways).
Using blit=False here allows to make the code more simple because we do not need to care about the differences between the animation on screen and the one saved - they are just the same.

The enumerate function yields both the index as well as the object from the enumerated sequence. I did use it here, because it is a convenient way to obtain both in the same loop. It is not at all important here, you could alternatively do something like

for i in range(len(barcollection)):
    barcollection[i].set_height(y[i])

enter image description here

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Wow! Thank you for that fast answer! Still, I have the following two questions: – Martin Monath Feb 09 '17 at 20:08
  • 1. So when does "blit=True" help? I see that you even set blit=False and still get the desired result? What did I understand wrong in the semantics about the blit variable? 2. Does the enumerate-function have a built-in return command? As far as I see [here](https://docs.python.org/2/library/functions.html#enumerate), it seems that enumerate provides a yield-command at the end. Is this important here? – Martin Monath Feb 09 '17 at 20:18
  • 1
    Ok, so maybe I should make my 2nd question more precise: why don't you have a "return" or "yield" -command at the end of your animate-function? Could it be that this is because barcollection is a global name? – Martin Monath Feb 11 '17 at 12:52
  • Anyway, thank you very much for your answers. Your solution solved my problem^^. – Martin Monath Feb 11 '17 at 12:53
  • The animate function needs to work with objects which are accessible from outside its scope. When using immutable types, this can be achieved using `global`, but this is not necessary here, since we are working on an instance of PatchCollection, which is accessible inside the animation function. All of this is completely unrelated to `return` or `yield`. In case blitting is used the animating function needs to return a list of artists to redraw. Since we are not using blitting here, no return value is required. – ImportanceOfBeingErnest Feb 11 '17 at 13:12
  • What if I want to plot a polar bar plot? I've set `projection = "polar"` but how do I set the angle the same way I set the height? – Abhishek Jul 03 '19 at 17:22
  • @Abhishek In a polar plot the x axis "winds around the origin" so to speak, so you need to change the x coordinate of the bar. – ImportanceOfBeingErnest Jul 03 '19 at 17:25
  • Do I have to do that inside the animation function or outside of the function where I'm creating the `barcollection`? – Abhishek Jul 03 '19 at 17:42
  • Also, what exactly is the y-axis value for the bars in the above example and how is it being set? Is it being set by the `plt.bar(x,barlist(1))` line or the `b.set_height()` part? Are they both doing the same thing? – Abhishek Jul 03 '19 at 19:48