Использование библиотеки Matplotlib. Как делать анимированные графики | jenyay.net

Использование библиотеки Matplotlib. Как делать анимированные графики

Введение

Анимированные графики в Matplotlib могут создаваться несколькими способами. Первый способ заключается в том, что можно самостоятельно обновлять график через заданный промежуток времени. Два других способа используют классы, производные от класса matplotlib.animation.Animation: FuncAnimation или ArtistAnimation. В данной статье будут рассмотрены все эти способы.

Все примеры в этой статье создают анимированный гауссов импульс, который движется с левого края графика до правого края.

Самостоятельное обновление графиков

При создании не анимированного графика его отображение осуществляется вызовом функции matplotlib.pyplot.show(), которая создает окно. Эта функция завершается только после закрытия окна, поэтому эту функцию нельзя использовать, если программа должна менять график в уже открытом окне.

Для решения этой проблемы есть возможность отображения и обновления окна графика без использования функции matplotlib.pyplot.show().

Для этого график нужно перевести в интерактивный режим с помощью функции matplotlib.pyplot.ion() (выключить интерактивный режим можно с помощью функции matplotlib.pyplot.ioff()).

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

Для начала более простой и медленный пример.

# -*- coding: utf-8 -*-

import time

import matplotlib.pyplot as plt
import numpy


def gaussian(x, delay, sigma):
    '''
    Функция, график которой будет отображаться процессе анимации
    '''

    return numpy.exp(-((x - delay) / sigma) ** 2)


if __name__ == '__main__':
    # Параметры отображаемой функции
    maxSize = 200
    sigma = 10.0

    # Диапазон точек для расчета графика функции
    x = numpy.arange(maxSize)

    # Значения графика функции
    y = numpy.zeros(maxSize)

    # !!! Включить интерактивный режим для анимации
    plt.ion()

    # У функции gaussian будет меняться параметр delay (задержка)
    for delay in numpy.arange(-50.0, 200.0, 1.0):
        y = gaussian(x, delay, sigma)

        # !!! Очистить текущую фигуру
        plt.clf()

        # Отобразить график
        plt.plot(x, y)

        # Установка отображаемых интервалов по осям
        plt.xlim(0, maxSize)
        plt.ylim(-1.1, 1.1)

        # !!! Следующие два вызова требуются для обновления графика
        plt.draw()
        plt.gcf().canvas.flush_events()

        # Задержка перед следующим обновлением
        time.sleep(0.02)

    # Отключить интерактивный режим по завершению анимации
    plt.ioff()

    # Нужно, чтобы график не закрывался после завершения анимации
    plt.show()

В этом примере до начала цикла график matplotlib переводится в интерактивный режим с помощью функции ion(). Затем в цикле производится очистка графика и добавление новой кривой с помощью функции plot(). Существенное отличие от простого рисования графиков заключается в том, что вместо функции show() вызывается функция draw(), после которой нужно дать возможность matplotlib обработать внутренние события, в том числе и для отображения графиков. Для этого используется вызов plt.gcf().canvas.flush_events(). Функция gcf() возвращает экземпляр типа Figure, отвечающий за окно графика.

После завершения цикла выключается интерактивный режим с помощью функции ioff(), а затем вызывается функция show(), чтобы показать пользователю окно с последним кадром анимации.

Как уже было сказано, этот пример работает медленно из-за того, что на каждом шаге анимации он удаляет все объекты кривых с графика, а затем создает новую кривую.

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

# -*- coding: utf-8 -*-

import time

import matplotlib.pyplot as plt
import numpy


def gaussian(x, delay, sigma):
    '''
    Функция, график которой будет отображаться процессе анимации
    '''

    return numpy.exp(-((x - delay) / sigma) ** 2)


if __name__ == '__main__':
    # Параметры отображаемой функции
    maxSize = 200
    sigma = 10.0

    # Диапазон точек для расчета графика функции
    x = numpy.arange(maxSize)

    # Значения графика функции
    y = numpy.zeros(maxSize)

    # !!! Включить интерактивный режим для анимации
    plt.ion()

    # Создание окна и осей для графика
    fig, ax = plt.subplots()

    # Установка отображаемых интервалов по осям
    ax.set_xlim(0, maxSize)
    ax.set_ylim(-1.1, 1.1)

    # Отобразить график фукнции в начальный момент времени
    line, = ax.plot(x, y)

    # У функции gaussian будет меняться параметр delay (задержка)
    for delay in numpy.arange(-50.0, 200.0, 1.0):
        y = gaussian(x, delay, sigma)

        # Обновить данные на графике
        line.set_ydata(y)

        # Отобразить новые данный
        fig.canvas.draw()
        fig.canvas.flush_events()

        # Задержка перед следующим обновлением
        time.sleep(0.02)

    # Отключить интерактивный режим по завершению анимации
    plt.ioff()
    plt.show()

По сути пример делает то же самое. Сначала с помощью функции subplots() создается окно с графиком. Эта функция возвращает объекты Figure (окно графика) и Axes (оси на графике).

Затем с помощью метода plot() класса Axes создается график. Этот метод возвращает список объектов Line2D. Поскольку известно, что мы добавляем только один график, то первый элемент сразу распаковывается в переменную line.

Внутри цикла вместо очистки графика и добавления новой кривой используется метод set_ydata() класса Line2D. Этот метод обновляет данные для данной кривой. После этого обновляется график с помощью методов draw() и flush_events().

Использование класса Animation

Для упрощения создания анимаций в Matplotlib существуют два класса: matplotlib.animation.FuncAnimation и matplotlib.animation.ArtistAnimation, все они производные от базового класса matplotlib.animation.Animation (строго говоря, есть еще промежуточный базовый класс matplotlib.animation.TimedAnimation). В документации к Matplotlib есть такая схема наследования:

Класс FuncAnimation используется, если нужно последовательно рассчитывать и обновлять график, как это делалось в предыдущих примерах. Класс ArtistAnimation используется, если все кривые (или другие анимированные объекты) рассчитываются заранее, а затем в каждом кадре они последовательно сменяют друг друга. Использование ArtistAnimation требует большего количества оперативной памяти, поскольку требуется заранее создать отдельные объекты для каждого кадра, которые будут последовательно отображаться, и все они хранятся в оперативной памяти.

Рассмотрим оба этих класса.

Создание анимации с помощью класса FuncAnimation

Идея использования класса FuncAnimation состоит в том, что конструктору этого класса помимо других параметров, о которых будет сказано позднее, передаются экземпляр класса matplotlib.figure.Figure (этот класс отвечает за окно с графиком) и функция, которая будет вызываться для каждого кадра анимации. Эта функция должна производить какие-то вычисления и возвращать список объектов, которые изменились в новом кадре. Строго говоря, эта функция должна возвращать список объектов, производных от базового класса matplotlib.artist.Artist. Все объекты, которые вы видите на графике являются производными от этого класса.

Пример с использованием класса FuncAnimation выглядит следующим образом:

# -*- coding: utf-8 -*-

import time

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy


def gaussian(x, delay, sigma):
    '''
    Функция, график которой будет отображаться процессе анимации.
    '''

    return numpy.exp(-((x - delay) / sigma) ** 2)


# Функция, вызываемая для каждого кадра
def main_func(frame, line, x, sigma):
    '''
    frame - параметр, который изменяется от кадра к кадру.
    line - кривая, для которой изменяются данные.
    x - список точек по оси X, для которых рассчитывается функция Гаусса.
    sigma - отвечает за ширину функции Гаусса.
    '''

    y = gaussian(x, frame, sigma)
    line.set_ydata(y)
    return [line]


if __name__ == '__main__':
    # Параметры отображаемой функции
    maxSize = 200
    sigma = 10.0

    # Диапазон точек для расчета графика функции
    x = numpy.arange(maxSize)

    # Значения графика функции
    y = numpy.zeros(maxSize)

    # Создание окна для графика
    fig, ax = plt.subplots()

    # Установка отображаемых интервалов по осям
    ax.set_xlim(0, maxSize)
    ax.set_ylim(-1.1, 1.1)

    # Создание линии, которую будем анимировать
    line, = ax.plot(x, y)

    # !!! Параметр, который будет меняться от кадра к кадру
    frames = numpy.arange(-50.0, 200.0, 1.0)

    # !!! Задержка между кадрами в мс
    interval = 30

    # !!! Использовать ли буферизацию для устранения мерцания
    blit = True

    # !!! Будет ли анимация циклической
    repeat = False

    # !!! Создание анимации
    animation = FuncAnimation(
            fig,
            func=main_func,
            frames=frames,
            fargs=(line, x, sigma),
            interval=interval,
            blit=blit,
            repeat=repeat)

    plt.show()

Функция, о которой только что говорилась в данном примере - функция main_func(). Она принимает как минимум один параметр frame, который будем меняться от кадра к кадру. В данном примере это смещение функции Гаусса по оси X. Эта функция передается в качестве второго параметра конструктору класса FuncAnimation. Остальные перечисленные ниже параметры являются необязательными:

  • frames задает некую изменяемую последовательность, каждый элемент которой для каждого кадра передается в функцию создания кадра. Этот параметр может быть: списком любых объектов; итератором; целым числом (это равносильно значению range(N)); функцией-генератором; None (равносильно передаче в качестве параметра itertools.count).
  • fargs - кортеж с дополнительными параметрами, которые передаются в функцию создания кадра. В данном примере, чтобы функция main_func() не использовала глобальные переменные (как это часто делают в примерах в документации), ей передаются те параметры, которые ей понадобятся для расчета и обновления кривой. Если не требуется передавать дополнительные параметры, этот параметр можно опустить, что будет равносильно передаче значения None.
  • interval - задержка между кадрами в миллисекундах.
  • blit - использовать ли буферизацию для уменьшения моргания графика при обновлении.
  • repeat. Если этот параметр равен True, то анимация начнется заново после достижения конца последовательности frames.

Кроме перечисленных параметров есть еще и другие необязательные параметры, которые позволяют изменять параметры кэширования данных и задержку между перезапуском анимации (параметр repeat_delay), если значение repeat равно True.

С точки зрения результата этот пример ничем не отличается от предыдущих.

Создание анимации с помощью класса ArtistAnimation

При использовании класса matplotlib.animation.ArtistAnimation нужно заранее создать список списков объектов для каждого кадра. То есть длина списка первого уровня соответствует количеству кадров, а каждый элемент этого списка - список объектов, которые будут показаны в данном кадре. При смене кадра объекты, предназначенные для других кадров скрываются.

Пример кода с использованием класса ArtistAnimation выглядит следующим образом:

# -*- coding: utf-8 -*-

import time

import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation
import numpy


def gaussian(x, delay, sigma):
    '''
    Функция, график которой будет отображаться процессе анимации
    '''

    return numpy.exp(-((x - delay) / sigma) ** 2)


if __name__ == '__main__':
    # Параметры отображаемой функции
    maxSize = 200
    sigma = 10.0

    # Диапазон точек для расчета графика функции
    x = numpy.arange(maxSize)

    # Значения графика функции
    y = numpy.zeros(maxSize)

    # Создание окна для графика
    fig, ax = plt.subplots()

    # Установка отображаемых интервалов по осям
    ax.set_xlim(0, maxSize)
    ax.set_ylim(-1.1, 1.1)

    # !!! Создание списка линий, которые будут последовательно
    # переключаться при изменении номера кадра
    frames = []
    for delay in numpy.arange(-50.0, 200.0, 1.0):
        y = gaussian(x, delay, sigma)
        line, = ax.plot(x, y, '-b')

        # !!! Поскольку на каждом кадре может меняться несколько объектов,
        # каждый элемент списка - это список изменяемых объектов
        frames.append([line])

    # Задержка между кадрами в мс
    interval = 30

    # Использовать ли буферизацию для устранения мерцания
    blit = True

    # Будет ли анимация циклической
    repeat = False

    # !!! Создание анимации
    animation = ArtistAnimation(
            fig,
            frames,
            interval=interval,
            blit=blit,
            repeat=repeat)

    plt.show()

В этом примере создается список списков frames, каждый элемент которого - список из одного экземпляра класса matplotlib.lines.Line2D, поскольку от кадра к кадру меняется только один объект на графике.

Конструктор ArtistAnimation кроме экземпляра класса Figure и списка объектов для кадров принимает параметры, аналогичные параметра класса FuncAnimation, о которых писалось выше: interval, repeat, repeat_delay и blit.

Этот пример выглядит практически так же, как и предыдущие за единственным исключением - цвет линии графика будет немного другой. Это связано с тем, что голубой цвет, заданный параметром '-b' метода plot() отличается от цвета графика по умолчанию. Однако если этот параметр не указать, то каждая новая линия будет создаваться с новым цветом, и при анимации цвет линии будет меняться.

Другие статьи про Matplotlib

Вы можете подписаться на новости сайта через RSS, Группу Вконтакте или Канал в Telegram.
4.5 stars

Рейтинг 4.1/5. Всего 30 голос(а, ов)



 11.03.2018 - 00:44

Искренне восхищен вашими трудами, но к сожалению данный пример не работает(окно выводится, а график нет).Возможно я что-то не понимаю, так как я в Питоне на уровне попыток освоения Tkinter

Александр 12.04.2019 - 15:06

Пример не работает

Программа выводит пустое окно графика без данных.
Скорее всего вся логика конструкции не верна.

Jenyay 15.06.2019 - 13:50

RE: Пример не работает

Да, при выходе более новой версии Matplotlib пример сломался (он чинился одной дополнительной строкой). Но я переписал полностью статью, дополнив ее другими способами создания анимации.


Подписаться на комментарии
Автор:
Тема:
 Ваш комментарий
 
 
Введите код 150