Makie.jl has extensive support for animations; you can create arbitrary plots, and save them to:

This is all made possible through the use of the ffmpeg tool, wrapped by FFMPEG.jl.

Have a peek at Interaction for some more information once you're done with this.

A simple example

Simple animations are easy to make; all you need to do is wrap your changes in the record function.

When recording, you can make changes to any aspect of the Scene or its plots.

Below is a small example of using record.

using Makie

 scene = lines(rand(10); linewidth=10)

 record(scene, "line_changing_colour.mp4", 1:255; framerate = 60) do i
        scene.plots[2][:color] = RGBf0(i/255, (255 - i)/255, 0) # animate scene
        # `scene.plots` gives the plots of the Scene.
        # `scene.plots[1]` is always the Axis if it exists,
        # and `scene.plots[2]` onward are the user-defined plots.

record(func, scene, path; framerate = 24)
record(func, scene, path, iter; framerate = 24)

Records the Scene scene after the application of func on it for each element in itr (any iterator). func must accept an element of itr.

The animation is then saved to path, with the format determined by path's extension. Allowable extensions are:

  • .mkv (the default, doesn't need to convert)
  • .mp4 (good for Web, most supported format)
  • .webm (smallest file size)
  • .gif (largest file size for the same quality)

.mp4 and .mk4 are marginally bigger and .gifs are up to 6 times bigger with the same quality!

Typical usage patterns would look like:

record(scene, "video.mp4", itr) do i
    func(i) # or some other manipulation of the Scene

or, for more tweakability,

record(scene, "test.gif") do io
    for i = 1:100
        func!(scene)     # animate scene
        recordframe!(io) # record a new frame

If you want a more tweakable interface, consider using VideoStream and save.


scene = lines(rand(10))
record(scene, "test.gif") do io
    for i in 1:255
        scene.plots[:color] = Colors.RGB(i/255, (255 - i)/255, 0) # animate scene


scene = lines(rand(10))
record(scene, "test.gif", 1:255) do i
    scene.plots[:color] = Colors.RGB(i/255, (255 - i)/255, 0) # animate scene
record(func, scene, path, iter; framerate = 24)

This is simply a shorthand to wrap a for loop in record.


    scene = lines(rand(10))
    record(scene, "test.gif", 1:100) do i
        scene.plots[:color] = Colors.RGB(i/255, 0, 0) # animate scene

In both cases, the returned value is a path pointing to the location of the recorded file.

Animation using time

To animate a scene, you can also create a Node, e.g.:

time = Node(0.0)

and use lift on the Node to set up a pipeline to access its value. For example:

scene = Scene()
time = Node(0.1)
myfunc(v, t) = sin.(v .* t)
positions = lift(t -> myfunc.(range(0, stop=2pi, length=50), t), time)
scene = lines!(scene, positions)

now, whenever the Node time is updated (e.g. when you push! to it), the plot will also be updated.

push!(time, Base.time())

You can also set most attributes equal to Observables, so that you need only update a single variable (like time) during your animation loop. A translation of the first example to this Observables paradigm is below:

using Makie

 "'Time' - an Observable that controls the animation"
 t = Node(0)

 "The colour of the line"
 c = lift(t) do t
         RGBf0(t/255, (255 - t)/255, 0)

 scene = lines(rand(10); linewidth=10, color = c)

 record(scene, "line_changing_colour_with_observables.mp4", 1:255; framerate = 60) do i
     t[] = i # update `t`'s value

A more complicated example:

using Makie

 scene = Scene()

 f(t, v, s) = (sin(v + t) * s, cos(v + t) * s, (cos(v + t) + sin(v)) * s)
 t = Node(Base.time()) # create a life signal
 limits = FRect3D(Vec3f0(-1.5, -1.5, -3), Vec3f0(3, 3, 6))
 p1 = meshscatter!(scene, lift(t-> f.(t, range(0, stop = 2pi, length = 50), 1), t), markersize = 0.05)[end]
 p2 = meshscatter!(scene, lift(t-> f.(t * 2.0, range(0, stop = 2pi, length = 50), 1.5), t), markersize = 0.05)[end]

 lines = lift(p1[1], p2[1]) do pos1, pos2
     map((a, b)-> (a, b), pos1, pos2)
 linesegments!(scene, lines, linestyle = :dot, limits = limits)
 # record a video
 N = 150
 record(scene, "record_video.mp4", 1:N) do i
     push!(t, Base.time())

Appending data to a plot

If you're planning to append to a plot, like a lines or scatter plot (basically, anything that's point-based), you will want to pass an Observable Array of Points to the plotting function, instead of passing x, y (and z) as separate Arrays. This will mean that you won't run into dimension mismatch issues (since Observables are synchronously updated).

TODO add more tips here

Animating a plot "live"

You can animate a plot in a for loop:

for i = 1:length(r)
    s[:markersize] = r[i]
    # AbstractPlotting.force_update!() is no longer needed

Similarly, for plots based on functions:

scene = Scene()
v = range(0, stop=4pi, length=50)
f(v, t) = sin(v + t) # some function
s = lines!(
    lift(t -> f.(v, t), time),

for i = 1:length(v)
    time[] = i

If you want to animate a plot while interacting with it, check out the async_latest function, and the Interaction section.

More complex examples

using Makie

 scene = Scene();
 function xy_data(x, y)
     r = sqrt(x^2 + y^2)
     r == 0.0 ? 1f0 : (sin(r)/r)

 r = range(-2, stop = 2, length = 50)
 surf_func(i) = [Float32(xy_data(x*i, y*i)) for x = r, y = r]
 z = surf_func(20)
 surf = surface!(scene, r, r, z)[end]

 wf = wireframe!(scene, r, r, lift(x-> x .+ 1.0, surf[3]),
     linewidth = 2f0, color = lift(x-> to_colormap(x)[5], surf[:colormap])
 N = 150
 record(scene, "animated_surface_and_wireframe.mp4", range(5, stop = 40, length = N)) do i
     surf[3] = surf_func(i)

You can see yet more complicated examples in the Example Gallery!