jonboy
jonboy

Reputation: 366

Animate gradient bar chart - matplotlib

The following code animates a bar chart. I'm trying to apply a gradient and text labels to this animation though. The autolabel() function aims to add the text label and the gradientbars()functions aims to add the gradient. When applying here functions however, the animation just produces a still image.

I'm also hoping to adjust the gradient to a fixed point. Specifically, rather than applying the gradient to the extent of the individual bar height, I'm hoping to apply a max level of 6. Please see figure below regarding this.

I've commented out the specific functions below so the code works but applying them will produced the figure attached.

fig, ax = plt.subplots()
ax.grid(False)

data = np.random.randint(5, size=(10, 5))

x = ['A','B','C','D','E']
plt.ylim(0, 6)

rects = plt.bar(x, data[0])

def autolabel(rects):

    '''
    Attach a text label above each bar displaying its height
    '''
    for rect in rects:
        height = rect.get_height()
        ax.text(rect.get_x() + rect.get_width()/2., 1.05 * height,
                '%d' % int(height),
                ha = 'center', va = 'bottom')

def gradientbars(bars):

    grad = np.atleast_2d(np.linspace(0,5,256)).T

    cmap = 'Blues'
    
    ax = bars[0].axes
    lim = ax.get_xlim() + ax.get_ylim()

    for bar in bars:
    
        bar.set_zorder(1)
        bar.set_facecolor("none")
    
        x,y = bar.get_xy()
        w,h = bar.get_width(), bar.get_height()    
   
        ax.imshow(grad, extent = [x, x + w, y, y + h], aspect = "auto", zorder = 0, cmap = cmap)
    
    ax.axis(lim)

#autolabel(rects)

#gradientbars(rects)

def animate(i):

    for rect, yi in zip(rects, data[i]):
        rect.set_height(yi)

anim = animation.FuncAnimation(fig, animate, frames = len(data), interval = 100)

plt.show()

Upvotes: 0

Views: 169

Answers (1)

Diziet Asahi
Diziet Asahi

Reputation: 40727

This is how I would do this:

def autolabel(rects):
    '''
    Attach a text label above each bar displaying its height
    '''
    ts = []
    for rect in rects:
        height = rect.get_height()
        t = ax.text(rect.get_x() + rect.get_width()/2., 1.05 * height,
                '%d' % int(height),
                ha = 'center', va = 'bottom')
        ts.append(t)
    return ts

def gradientbars(bars, cmap, vmin, vmax):
    g = np.linspace(vmin,vmax,100)
    grad = np.vstack([g,g]).T
    xmin,xmax = ax.get_xlim()
    ymin,ymax = ax.get_ylim()
    ims = []
    for bar in bars:
        bar.set_facecolor('none')
        im = ax.imshow(grad, aspect="auto", zorder=0, cmap=cmap, vmin=vmin, vmax=vmax, extent=(xmin,xmax,ymin,ymax))
        im.set_clip_path(bar)
        ims.append(im)
    return ims

Nbars=5
Nframes=10
vmin=0
vmax=6
cmap = 'Blues'
data = np.random.choice([0,1,2],size=(Nframes,Nbars))
data = data.cumsum(axis=1)
data[data>6] = 6
x=[chr(ord('A')+i) for i in range(Nbars)]
print(x)
print(data)

fig, ax = plt.subplots()
ax.grid(False)
plt.ylim(vmin, vmax)
rects = plt.bar(x,data[0])
labels = autolabel(rects)
imgs = gradientbars(rects, cmap=cmap, vmin=vmin, vmax=vmax)

def animate(i):
    for rect,label,img,yi in zip(rects, labels, imgs, data[i]):
        rect.set_height(yi)
        label.set_text('%d'%int(yi))
        label.set_y(yi)
        img.set_clip_path(rect)

anim = animation.FuncAnimation(fig, animate, frames = len(data), interval = 500)
plt.show()

enter image description here

EDIT Same with negative numbers

Nbars = 5
Nframes = 50
dt=50
cmap='coolwarm_r'
data = np.zeros(shape=(Nframes,Nbars))
for i in range(Nbars):
    d = np.sin(np.arange(0,Nframes*(dt*1e-3),dt*1e-3)*0.5*np.pi+np.random.uniform(low=-1., high=1.))
    data[:,i] = d
x = [chr(ord('A')+i) for i in range(Nbars)]
vmin = -1.
vmax = 1.


def gradientbars(bars, cmap, vmin, vmax):
    g = np.linspace(vmin,vmax,100)
    grad = np.vstack([g,g]).T
    xmin,xmax = ax.get_xlim()
    ymin,ymax = ax.get_ylim()
    ims = []
    for bar in bars:
        bar.set_facecolor('none')
        im = ax.imshow(grad, aspect="auto", zorder=0, cmap=cmap, vmin=vmin, vmax=vmax, extent=(xmin,xmax,ymin,ymax))
        im.set_clip_path(bar)
        ims.append(im)
    return ims

def autolabel(rects):
    '''
    Attach a text label above each bar displaying its height
    '''
    ts = []
    for rect in rects:
        height = rect.get_height()
        va = 'bottom' if height>=0 else 'top'
        t = ax.text(rect.get_x() + rect.get_width()/2., height,
                f'{height:.2f}',
                ha ='center', va=va)
        ts.append(t)
    return ts


with plt.style.context('dark_background'):
    fig, ax = plt.subplots()
    ax.set_ylim(vmin,vmax)

    rects = ax.bar(x=x, height=data[0,:])
    imgs = gradientbars(rects, cmap, vmin, vmax)
    labels = autolabel(rects)
    sns.despine(ax=ax, left=True, right=True, bottom=True, top=True)
    ax.set_yticks([])


def animate(i, data, rects, imgs, labels):
    for j,(rect,img,label) in enumerate(zip(rects,imgs,labels)):
        rect.set_height(data[i,j])
        img.set_clip_path(rect)
        label.set_y(data[i,j])
        label.set_va('bottom' if data[i,j]>=0 else 'top')
        label.set_text(f'{data[i,j]:.2f}')

ani = animation.FuncAnimation(fig, animate, frames=Nframes, fargs=(data, rects, imgs, labels), interval=dt)

enter image description here

Upvotes: 2

Related Questions