0

I have been trying to create a horizontal bar chart where the title and data change during each frame. The issue I am running into is that if I use blit=True, the data updates but not the title. When I use blit=False the title changes but not the data (it only increases).

I have read through dozens of answers and tried everything including set_title and set_text but I am at a total loss. Thank you for your help.

%matplotlib
import pandas as pd
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 
import numpy as np 
import csv

people = ('','Jim', 'Dan')
plt.rcdefaults()
fig, ax = plt.subplots()
y_pos = np.arange(len(people))

ax.set_xlim(0,10)
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.invert_yaxis() 
ax.set_xlabel('Skill')
titleList=['Basketball','Hockey']
df=[[0,5,7],[0,4,9]]
title = ax.text(0.5,0.95, "Test", bbox={'facecolor':'w', 'alpha':0.5, 'pad':5},transform=ax.transAxes, ha="center")
def animate(i):
       # Example data
    while i<2:
        ax.set_yticks(y_pos)
        ax.set_yticklabels(people)
        ax.set_xlabel(titleList[i])
        performance=df[i]
        title.set_text(str(titleList[i]))
        line= ax.barh(y_pos, performance, align='center',
                color='blue', ecolor='None')
        return line

ani = animation.FuncAnimation(fig,animate, frames=5, blit=True
                            ,interval=2000,repeat=False)

plt.show() 
Jimmy
  • 127
  • 10

1 Answers1

1
  1. You call a FuncAnimation() with frames=5, animate(i) will thus try to set label and titel via titleList[i] which however only has 2 entries. Especially with blit=True, this will throw errors.

  2. Your animate() functions returns line; if we print(line), we find it is rather a <BarContainer object of 3 artists> than a line, i.e. the three rectangles of barh(). You should rather store the barh() in rects and then return [rect for rect in rects], see this question

Complete Code:

import pandas as pd
import matplotlib as mpl ## uncomment this if you are running this on a Mac
mpl.use('TkAgg')         ## and want to use blit=True
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 
import numpy as np 
import csv

people = ('','Jim', 'Dan')
plt.rcdefaults()
fig, ax = plt.subplots()
y_pos = np.arange(len(people))

ax.set_xlim(0,10)
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.invert_yaxis() 
ax.set_xlabel('Skill')
titleList=['Basketball','Hockey']
df=[[0,5,7],[0,4,9]]
def animate(i):
   # Example data
    while i<2:
        ax.set_yticks(y_pos)
        ax.set_yticklabels(people)
        ax.set_xlabel(titleList[i])
        performance=df[i]

        title = ax.text(0.5,0.95,str(titleList[i]), bbox={'facecolor':'w', 'alpha':0.5, 'pad':5},transform=ax.transAxes, ha="center")

        rects = ax.barh(y_pos, performance, align='center',
                color='blue', ecolor='None')
        return [rect for rect in rects] + [title]


ani = animation.FuncAnimation(fig,animate, frames=2, blit=True
                            ,interval=2000,repeat=False)

plt.show() 
Asmus
  • 5,117
  • 1
  • 16
  • 21
  • Thank you for your answer. 1. You're right. I had it as 5 because that's the real size but I took away the titles in order to have a more compact example. I have changed this. 2. I am not sure how to apply this to my example. Since set_height and set_width don't exist in **barh**, what would I assign as **rect**. Thank you so much again! – Jimmy May 09 '19 at 19:17
  • 1
    @Jimmy I've updated my answer with the complete code. – Asmus May 09 '19 at 19:24
  • Thank you again. The only issue is that because you're using **blit=False** the rectangles do not get updated when the number (width) is lower than in the previous frame, which is the same issue I already had before. – Jimmy May 09 '19 at 19:30
  • Set `blit=True` then? – Asmus May 09 '19 at 19:36
  • Then the title doesn't change. This was the original dilemma. Need to change both the rectangles and the title. – Jimmy May 09 '19 at 19:37
  • Ooops, I totally forgot about that, I am sorry. Updated my answer again, now the `title=ax.text()` line is *inside* the animate function, and is returned as an iterable artist list, i.e. `[title]` – Asmus May 09 '19 at 19:44
  • Thank you!! Accepted. Only explanation I am curious about is how does the **AnimationFunc** know what the data vs what the title is. Thank you again!! – Jimmy May 09 '19 at 19:45
  • @Jimmy also note that you could use `for i in range(len(rects)):` to iterate over the individual bars and then run `rects[i].set_width()` to set the width, see [here](https://stackoverflow.com/a/42143866/565489) – Asmus May 09 '19 at 19:56