Анимация метки с помощью гистограммы - matplotlib - PullRequest
0 голосов
/ 04 августа 2020

Приведенный ниже код анимирует гистограмму и связанные значения меток. Проблема, с которой я столкнулся, заключается в размещении метки, когда целое число отрицательно. В частности, я хочу, чтобы метка располагалась поверх панели, а не внутри нее. Он работает для первого кадра, но последующие кадры анимации возвращаются к нанесению метки внутри гистограммы для отрицательных целых чисел.

def autolabel(rects, ax):

    # Get y-axis height to calculate label position from.
    ts = []

    (y_bottom, y_top) = ax.get_ylim()
    y_height = y_top - y_bottom

    for rect in rects:

        height = 0
        if rect.get_y() < 0:
            height = rect.get_y()
        else:
            height = rect.get_height()

        p_height = (height / y_height)

        if p_height > 0.95: 
            label_position = height - (y_height * 0.05) if (height > -0.01) else height + (y_height * 0.05) 
        else:
            label_position = height + (y_height * 0.01) if (height > -0.01) else height - (y_height * 0.05) 

        t = ax.text(rect.get_x() + rect.get_width() / 2., label_position,
                '%d' % int(height),
                ha='center', va='bottom')
    
        ts.append(t)
    
    return ts

def gradientbars(bars, ax, 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

vmin = -6
vmax = 6
cmap = 'PRGn'

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

x = [chr(ord('A')+i) for i in range(4)]

fig, ax = plt.subplots()
ax.grid(False)
ax.set_ylim(vmin, vmax)
rects = ax.bar(x,data[0])

labels = autolabel(rects, ax)
imgs = gradientbars(rects, ax, 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()

1 Ответ

1 голос
/ 04 августа 2020

Это работает для первого кадра.

Вы вызываете autolabel(rects, ax) на первом графике, поэтому метка размещена правильно.

Последующие кадры анимации возвращаются к отображению метки внутри гистограммы для отрицательных целых чисел.

Положение метки последующих кадров устанавливается label.set_y(yi). yi взято из data[i], вы не учли здесь отрицательное значение.

Я создаю функцию с именем get_label_position(height), чтобы вычислить правильную позицию метки для заданной высоты. Он использует глобальную переменную y_height. И вызовите эту функцию перед label.set_y().

import matplotlib.pyplot as plt
from matplotlib import animation
import pandas as pd
import numpy as np


def get_label_position(height):
    p_height = (height / y_height)

    label_position = 0
    if p_height > 0.95: 
        label_position = height - (y_height * 0.05) if (height > -0.01) else height + (y_height * 0.05)
    else:
        label_position = height + (y_height * 0.01) if (height > -0.01) else height - (y_height * 0.05)

    return label_position


def autolabel(rects, ax):

    # Get y-axis height to calculate label position from.
    ts = []

    (y_bottom, y_top) = ax.get_ylim()
    y_height = y_top - y_bottom

    for rect in rects:

        height = 0
        if rect.get_y() < 0:
            height = rect.get_y()
        else:
            height = rect.get_height()

        p_height = (height / y_height)

        if p_height > 0.95: 
            label_position = height - (y_height * 0.05) if (height > -0.01) else height + (y_height * 0.05) 
        else:
            label_position = height + (y_height * 0.01) if (height > -0.01) else height - (y_height * 0.05) 

        t = ax.text(rect.get_x() + rect.get_width() / 2., label_position,
                '%d' % int(height),
                ha='center', va='bottom')
    
        ts.append(t)
    
    return ts

def gradientbars(bars, ax, 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

vmin = -6
vmax = 6
cmap = 'PRGn'

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

x = [chr(ord('A')+i) for i in range(4)]

fig, ax = plt.subplots()
ax.grid(False)
ax.set_ylim(vmin, vmax)
rects = ax.bar(x,data[0])

labels = autolabel(rects, ax)
imgs = gradientbars(rects, ax, cmap=cmap, vmin=vmin, vmax=vmax)

(y_bottom, y_top) = ax.get_ylim()
y_height = y_top - y_bottom

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(get_label_position(yi))
        img.set_clip_path(rect)


anim = animation.FuncAnimation(fig, animate, frames = len(data), interval = 500)
plt.show()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...