Документация Engee

Анимации

Страница в процессе перевода.

С помощью Makie можно легко создавать анимированные графики. Анимация производится путем внесения изменений в данные или атрибуты графика в виде наблюдаемых объектов и покадровой записи изменяющегося рисунка. Дополнительные сведения о рабочем процессе наблюдаемых объектов см. на странице Наблюдаемые объекты.

Простой пример

Для создания анимации необходимо использовать функцию record.

Сначала создается объект Figure. Далее передается функция, которая покадрово изменяет этот рисунок для записи с помощью record. Любые изменения, внесенные в рисунок или его графики, будут отражены в итоговой анимации. Кроме того, необходимо передать итерируемый объект, содержащий столько элементов, сколько кадров должно быть в анимации. Функция, переданная в первом аргументе, вызывается для каждого элемента этого итератора в течение анимации.

Для начала покажем, как можно изменить цвет линейного графика.

using GLMakie
using Makie.Colors

fig, ax, lineplot = lines(0..10, sin; linewidth=10)

# настройки анимации
nframes = 30
framerate = 30
hue_iterator = range(0, 360, length=nframes)

record(fig, "color_animation.mp4", hue_iterator;
        framerate = framerate) do hue
    lineplot.color = HSV(hue, 1, 0.75)
end

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

function change_function(hue)
    lineplot.color = HSV(hue, 1, 0.75)
end

record(change_function, fig, "color_animation.mp4", hue_iterator; framerate = framerate)

Форматы файлов

Видеофайлы создаются с помощью FFMPEG_jll.jl. На выбор доступны следующие форматы файлов.

  • .mkv (по умолчанию, не требует конвертации)

  • .mp4 (хорошо подходит для Интернета, широко поддерживается)

  • .webm (наименьший размер файла)

  • .gif (наихудшее качество при самом большом размере файла)

Анимации с использованием Observables

Часто требуется анимировать сложный график с течением времени, и все отображаемые данные должны определяться текущей меткой времени. Такую зависимость очень легко выразить с помощью Observables.

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

Вот пример, в котором строятся графики двух различных функций. Значения y каждой из них зависят от времени, поэтому для изменения обоих графиков достаточно изменить только время. Мы используем вспомогательный макрос @lift, который указывает, что повышаемое (lift) выражение зависит от каждого наблюдаемого объекта со знаком $.

using GLMakie

time = Observable(0.0)

xs = range(0, 7, length=40)

ys_1 = @lift(sin.(xs .- $time))
ys_2 = @lift(cos.(xs .- $time) .+ 3)

fig = lines(xs, ys_1, color = :blue, linewidth = 4,
    axis = (title = @lift("t = $(round($time, digits = 1))"),))
scatter!(xs, ys_2, color = :red, markersize = 15)

framerate = 30
timestamps = range(0, 2, step=1/framerate)

record(fig, "time_animation.mp4", timestamps;
        framerate = framerate) do t
    time[] = t
end

Большинство атрибутов графика можно установить равными Observable, что позволит обновлять лишь одну переменную (например, время) во время цикла анимации.

Например, чтобы создать линию, цвет которой зависит от времени, можно использовать следующий код.

using GLMakie

time = Observable(0.0)
color_observable = @lift(RGBf($time, 0, 0))

fig = lines(0..10, sin, color = color_observable)

framerate = 30
timestamps = range(0, 2, step=1/framerate)

record(fig, "color_animation_2.mp4", timestamps; framerate = framerate) do t
    time[] = t
end

Добавление данных с помощью Observables

Во время анимации можно также добавлять данные к графику. Вместо того чтобы передавать значения x и y (или z) по отдельности, лучше создать объект Observable с вектором точек (Point), чтобы количество значений x и y всегда совпадало.

using GLMakie

points = Observable(Point2f[(0, 0)])

fig, ax = scatter(points)
limits!(ax, 0, 30, 0, 30)

frames = 1:30

record(fig, "append_animation.mp4", frames;
        framerate = 30) do frame
    new_point = Point2f(frame, frame)
    points[] = push!(points[], new_point)
end

Динамическая анимация графика

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

points = Observable(Point2f[randn(2)])

fig, ax = scatter(points)
limits!(ax, -4, 4, -4, 4)

fps = 60
nframes = 120

for i = 1:nframes
    new_point = Point2f(randn(2))
    points[] = push!(points[], new_point)
    sleep(1/fps) # обновляет отображаемые данные!
end

Еще один пример — обновление содержимого тепловой карты.

using GLMakie

function mandelbrot(x, y)
    z = c = x + y*im
    for i in 1:30.0; abs(z) > 2 && return i; z = z^2 + c; end; 0
end

x = LinRange(-2, 1, 200)
y = LinRange(-1.1, 1.1, 200)
matrix = mandelbrot.(x, y')
fig, ax, hm = heatmap(x, y, matrix)

N = 50
xmin = LinRange(-2.0, -0.72, N)
xmax = LinRange(1, -0.6, N)
ymin = LinRange(-1.1, -0.51, N)
ymax = LinRange(1, -0.42, N)

# мы используем `record`, чтобы показать полученное видео в документах.
# Если записывать видео не нужно, подойдет и обычный цикл.
# Просто не забудьте вызвать `display(fig)` перед циклом
# и, если запись не происходит, необходимо вставить yield для выдачи результата задаче отрисовки
record(fig, "heatmap_mandelbrot.mp4", 1:7:N) do i
    _x = LinRange(xmin[i], xmax[i], 200)
    _y = LinRange(ymin[i], ymax[i], 200)
    hm[1] = _x # обновляем координаты x
    hm[2] = _y # обновляем координаты y
    hm[3] = mandelbrot.(_x, _y') # обновляем данные
    autolimits!(ax) # обновляем пределы
    # yield() -> не требуется при записи
end