Skip to content

Makie.jl (Julia) ​

GPU-first plotting for Julia built on a reactive Observable model and three interchangeable backends (CairoMakie, GLMakie, WGLMakie) that render the same Figure — the survey's cleanest exemplar of the reactive/observable execution model, where a record loop animates by mutating observables, not by morphing objects.

FieldValue
LanguageJulia
LicenseMIT — "The MIT License (MIT)", Copyright (c) 2018-2021: Simon Danisch, Julius Krumbiegel (LICENSE.md)
RepositoryMakieOrg/Makie.jl
Documentationdocs.makie.org
CategoryReactive/observable plotting & data-visualization ecosystem — not a morph-animation engine
First release2018 (MIT copyright 2018-2021); JOSS paper Sept 2021; current v0.24.13 (Jul 7 2026); reviewed Jul 11 2026
BackendsCairoMakie (CPU vector), GLMakie (OpenGL), WGLMakie (WebGL), RPRMakie (raytrace, experimental)
Timing/reactive modelObservables (via Observables.jl) + a record frame loop — the reactive model
Video outputrecord / VideoStream piping to FFMPEG_jll (.mp4, .webm, .mkv, .gif)

NOTE

Makie is a plotting framework, not a Manim-class morph engine. It has no Transform-between-shapes primitive, no rate-function/easing library, and no point-count alignment — those axis-2 concerns are N/A and marked so below. It earns its place in the survey as the reference implementation of the other animation paradigm: the reactive/observable model, where the author changes data and the scene recomputes, and "animation" is that change captured frame by frame.


Overview ​

What it solves ​

Makie is a general-purpose scientific plotting stack for Julia — scatter, line, surface, heatmap, mesh, volume, and 3D plots — with a single frontend package (Makie) and swappable rendering backends. The README's positioning line:

"Makie is an interactive data visualization and plotting ecosystem for the Julia programming language, available on Windows, Linux and Mac." — README.md

The problem it uniquely solves for this survey is interactive, data-driven recomputation: because every plottable value is an Observable, changing an input immediately updates the view, and the same mechanism that drives a live GUI also drives an exported animation. Manim expresses time with an imperative play-loop over a retained scene graph of morphing objects; Makie expresses it as a dataflow graph of observables that a record loop advances.

Design philosophy ​

The documentation homepage states the philosophy in one line:

"Makie is a data visualization ecosystem for the Julia programming language, with high performance and extensibility." — docs.makie.org

Two commitments follow. First, one frontend, many backends: the Makie package defines the plot objects, attributes, and layout; the backend packages render them. The README:

"The backend packages GLMakie, WGLMakie, CairoMakie and RPRMakie add different functionalities: You can use Makie to interactively explore your data … export high-quality vector graphics or even raytrace with physically accurate lighting." — README.md

Makie itself is never installed directly — "There's no need to install Makie.jl separately, it is re-exported by each backend package" (docs). Second, reactive attributes: plot attributes are observables, so "interaction and animations … can be handled using Observables.jl" (observables docs). This is the design bet that makes Makie the survey's reactive-model exemplar.


How it works ​

An Observable is the atom. Observables.jl (re-exported by Makie) gives a container whose value is read with empty-bracket x[], written with x[] = v, and — crucially — notifies listeners on write:

julia
# Observable basics: read x[], write x[] = v, react with on()
x = Observable(0.0)
on(x) do val
    println("New value of x is $val")   # runs synchronously on every write
end
x[] = 3.34                              # prints; listeners fire in registration order

"An Observable is a container object whose stored value you can update interactively." — observables docs

Derived observables are built with lift (and its @lift macro sugar), which re-evaluate whenever any input changes:

julia
# Derived observables: y tracks f(x); @lift lifts every $-marked observable
y = lift(a -> a^2, x)     # whenever x changes, y[] == x[]^2
z = @lift($x .+ $y)       # $ marks each observable dependency

"Now, whenever x changes, the derived Observable y will immediately hold the value f(x)." — observables docs

Plotting builds a scene from these atoms. A Figure holds a top-level Scene and a GridLayout; an Axis is placed at a grid cell; a mutating plot call (scatter!, lines!, mesh!) adds a plot object whose arguments become observables:

julia
# Figure → Axis at grid cell → mutating plot call returns a plot object
fig = Figure()
ax  = Axis(fig[1, 1])
sc  = scatter!(ax, xs, ys)   # `!` mutates the axis; sc is the Plot object

Animation is then just mutation over an iterator, captured by record:

julia
# Animation = advance an iterator, mutate observables, capture each frame
record(fig, "color_animation.mp4", hue_iterator; framerate = framerate) do hue
    plot.color[] = to_colormap(:viridis)[hue]   # mutate → scene recomputes → frame
end

"Animations work by making changes to data or plot attribute Observables and recording the changing figure frame by frame." — animation docs

The rest of this page walks the survey's eight axes against this machinery.


Object & scene model ​

Makie is retained-mode: you build a persistent object tree once and mutate it, and the backend re-renders it each frame. But its retained unit is not a Manim Mobject — there is no piecewise-Bézier VMobject, no submobject "family", and no morph alignment. Makie's tree is a Scene graph of Plot objects.

  • Scene — "A Scene is like a container for Plots and other Scenes", and "Scenes have Plots and Subscenes associated with them" (scenes docs). Scenes nest: Scene(parentscene) makes a subscene, and "A subscene is no different than a normal Scene, except that it is linked to a 'parent' Scene." Each carries an affine transform — "Every Scene also has a transformation, made up of _scale, translation, and rotation"_ — plus a camera (camera(scene)). This is the survey's scene-graph node.
  • Figure — the user-facing container. "The Figure object contains a top-level Scene and a GridLayout, as well as a list of blocks that have been placed into it, like Axis, Colorbar, Slider, Legend, etc." (figure docs). Blocks are placed by grid indexing: ax = fig[1, 1] = Axis(fig).
  • Plot objects — scatter!, lines!, mesh!, heatmap!, surface!, volume!. The bang forms add into an existing axis: "You can plot into an existing axis with plotting functions that end with a !" (getting-started, e.g. scatter!(ax, seconds, measurements)). A plot's positional arguments and keyword attributes are all observables, which is what makes the reactive update loop possible.

NOTE

The Scene is now largely internal: "Before the introduction of the Figure workflow, Scenes used to be the main container object … Now, scenes are mostly an implementation detail for many users." (scenes docs). Most authoring is against Figure/Axis, with the scene tree underneath.


Animation & timing model ​

This is Makie's defining axis and the reason it is in the survey. Its execution model is reactive/observable: values are observables, the view recomputes when they change, and a record loop advances them. The concepts page names Makie as this model's exemplar, and contrasts it with the imperative engines' emulation via a ValueTracker — Makie's Observableis the primitive that a ValueTracker reimplements inside a play-loop engine.

The loop is the whole model. record takes a figure, a path, and an iterable, and calls a user function per element (documented signatures: record(func, figurelike, path; …) and record(func, figurelike, path, iter; …), recording.jl). The body mutates observables; each iteration a frame is captured. Manually, the equivalent is a loop that calls recordframe!(io) after mutating the figure (recording.jl).

What Makie does not provide — the Manim axis-2 machinery — is instructive by its absence:

Manim axis-2 conceptMakie
Interpolation / lerpN/A in core. The author writes the tween (iterate a LinRange, set obs[] = a + (b-a)*t); no built-in point/color lerp.
Rate function / easingN/A in core. No smooth/rush_into library; ease by transforming the loop parameter yourself (companion Animations.jl exists but is not part of Makie).
Transform + point-count alignmentN/A. No shape-to-shape morph; there is no Bézier path store to align.
Updaters / ValueTrackerNative, inverted. lift/@lift and on are the reactive graph the imperative engines emulate.

So Makie's "animation" is a dataflow re-computation, not a timeline of eased morphs. The rate-functions.d probe's easing tables describe what a Manim-class engine bakes in and what a Makie author must supply by hand around the record loop.


Rendering backend & rasterization ​

One Figure, several rasterizers — the axis where Makie's "one frontend, many backends" design meets the survey's CPU-vector vs GPU-vector distinction. A backend is chosen by using CairoMakie + CairoMakie.activate!() (or GLMakie, WGLMakie); the same plotting code then rasterizes differently.

BackendEngine / targetCharacter
CairoMakie"Cairo.jl based, non-interactive 2D (and some 3D) backend for publication-quality vector graphics" (backends)CPU vector, deterministic, SVG/PDF/PNG
GLMakie"GPU-powered, interactive 2D and 3D plotting in standalone GLFW.jl windows" (backends)OpenGL GPU, interactive native window, bitmap
WGLMakie"WebGL-based interactive 2D and 3D plotting that runs within browsers" (backends)WebGL GPU, browser/notebook interactive
RPRMakie"An experimental ray tracing backend" (backends)RadeonProRender, physically-based (experimental)

The CPU-vs-GPU trade-off is stated plainly for CairoMakie:

"You should use it if you want to achieve the highest-quality plots for publications, as the rendering process of the GL backends works via bitmaps and is geared more towards speed than pixel-perfection." — CairoMakie docs

That is exactly the survey's resolution: the CPU vector backend is the reproducible oracle (Cairo draws analytic vector paths to SVG/PDF), the GPU backends are the fast/interactive path (bitmap rasterization, not bit-identical across drivers). CairoMakie exposes resolution knobs px_per_unit (raster density) and pt_per_unit (vector point scale) (CairoMakie docs).

Anti-aliasing and color model / gamma therefore differ by backend: CairoMakie gets Cairo's analytic-coverage AA on vector edges, while the GL backends multisample. The one text-specific rasterization fact Makie documents — GL glyphs are signed-distance fields — is covered under Typesetting & text. The frame-capture.d probe stands in for the CPU coverage-AA + readback path a CairoMakie render performs.


Typesetting & text ​

Makie loads fonts through FreeType and does its own glyph layout, rather than routing text through a LaTeX install the way both Manim forks do.

  • Font loading — "Makie uses the FreeType.jl package for font support, therefore, most fonts that this package can load should be supported by Makie as well." (fonts docs). Layout goes through FreeTypeAbstraction (glyph_index, layout_text) building a GlyphCollection of positioned glyphs — the survey's text-shaping step (decide which glyph goes where) followed by glyph-outline extraction.
  • GL backends use SDF glyphs — the GL/WGL path rasterizes each glyph from a signed distance field: the fonts docs note this "can only be used to render monochrome glyphs, but not arbitrary bitmaps" (fonts docs), which is why emoji/color fonts do not render on those backends. CairoMakie, by contrast, draws the glyph outlines directly through Cairo.
  • Math via a native engine, not a TeX toolchain — LaTeXStrings are laid out by MathTeXEngine.jl, a pure-Julia math typesetter. This is the sharpest contrast with Manim's LaTeX→dvisvgm→SVG pipeline: Makie needs no TeX distribution on the system to typeset an equation, trading TeX's completeness for a self-contained, faster path.

Output & encoding ​

Video output is a record/VideoStream pipeline to an FFmpeg subprocess — the survey's frame-capture-and-readback followed by codec/muxing/pixel-format.

Readback. Each frame is captured by reading the backend's pixels: record (and manual recordframe!(io)) calls colorbuffer(screen) to pull the rendered image out of the backend into a buffer (recording.jl) — the exact "render → framebuffer readback" step the frame-capture.d probe models.

Encoding. The buffer is written to an FFmpeg process. From ffmpeg-util.jl:

  • The VideoStream holds io::Base.PipeEndpoint, process::Base.Process, a buffer::Matrix{RGB{N0f8}}, and a VideoStreamOptions (fields format, framerate, compression, profile, pixel_format, loop, …).
  • Frames pipe to an open(pipeline(...), "w") writing to an FFmpeg process spawned via FFMPEG_jll.ffmpeg() — the encoder is FFMPEG_jll, as the animation docs confirm: "Video files are created with FFMPEG_jll.jl." (animation docs).
  • Pixel format: input is rgb24; .mp4 output defaults to yuv420p (yuv444p under a high444 profile). Codec: .mp4 → libx264, .webm → libvpx-vp9. Containers: "mkv", "mp4", "webm", "gif". Default framerate: 24.

Image export. Still frames go through save: "CairoMakie uses Cairo.jl to draw vector graphics to SVG and PDF" (CairoMakie docs), e.g. save("figure.pdf", fig, pdf_version="1.4") or save("plot.svg", fig); the GL backends save bitmap PNG. Backend output type is selectable — CairoMakie.activate!(type = "svg") vs type = "png".


Interactivity, preview & authoring ​

The same observable graph that animates also drives live interaction — the axis where Makie's reactive model pays off twice. Because attributes are observables and on(observable) registers a synchronous callback, a native GLMakie window or a browser WGLMakie view recomputes in place as the user drags, zooms, or moves a Slider block; the figure holds those interactive blocks (Slider, Button, Menu) alongside the axes.

Authoring therefore has two modes off one code path:

  • Interactive preview — GLMakie opens a real OpenGL window ("interactive 2D and 3D plotting in standalone GLFW.jl windows", backends); WGLMakie embeds the same figure in a browser, notebook, or IDE. Events (mouse, keyboard, events(scene)) feed observables.
  • Deterministic export — the same figure and the same observable mutations, driven by a record iterator instead of a human, produce a video or image via CairoMakie (for reproducible vector output) or a GL backend.

This is the practical realization of the survey's CPU-oracle / GPU-preview split: interact on the GPU, export the archival copy on the CPU vector backend, from one script.


Extensibility & API surface ​

The frontend Makie package is the API surface; backends re-export it, so user code and third-party recipes are backend-agnostic. Extensibility is explicit in the design philosophy quote ("high performance and extensibility", docs) and rests on three seams:

  • @recipe / convert_arguments — a plot type is defined once as a recipe (a function that emits other plots plus an attribute schema) and works on every backend; convert_arguments teaches Makie how to turn a user type into plottable primitives. This is how domain packages add first-class plots without touching a backend.
  • Attributes as observables — every plot exposes a keyword attribute set (color, markersize, linewidth, colormap, …) that are observables; themes (set_theme!, with_theme) override defaults globally.
  • ComputeGraph — the 0.24 attribute-processing layer. The observables docs: "Makie 0.24 introduced the ComputeGraph for processing updates within plots. With that Makie.update!(plot, attribute1 = new_value1, …) was added, which can be used instead of updating Observables." (observables docs). It batches and incrementally propagates attribute updates through a plot.

The plot vocabulary itself is broad and 2D/3D: scatter, lines, linesegments, heatmap, image, surface, mesh, volume, contour, band, poly, plus Axis/Axis3/LScene and the block ecosystem (Colorbar, Legend, Slider).


Determinism, caching & performance ​

Determinism is a per-backend property, and Makie has no cross-run frame cache of the kind Manim relies on.

  • Deterministic sampling — CairoMakie's analytic vector rasterization is the reproducible path (SVG/PDF are backend-stable; PNG is bit-stable for a fixed Cairo build). The GL backends are explicitly not pixel-perfect — "the rendering process of the GL backends works via bitmaps and is geared more towards speed than pixel-perfection" (CairoMakie docs) — so they vary with driver/MSAA, exactly the CPU-vs-GPU reproducibility gap. The frame-capture.d probe's FNV-1a checksum illustrates the determinism a Cairo render would give.
  • Content-hash caching — N/A. Makie does not hash play()-equivalent units and reuse partial movie files; there is no per-call render cache. Each record iteration re-renders the figure. This is a genuine finding: the caching that makes Manim's iterate-render loop fast has no analog here, because Makie's iteration granularity is a full frame, not a cacheable animation segment.
  • Incremental recompute (the reactive substitute) — what Makie has instead is intra-frame incrementalism: the observable graph (and the ComputeGraph) only re-runs the lifts downstream of a changed input, so mutating one attribute does not recompute the whole scene. That is a different lever from content-hash caching — dataflow minimality within a frame, not cross-run reuse across frames.
  • Performance — the GL backends are GPU-accelerated for large scatter/line/mesh data and real-time interaction; the JOSS positioning is "high performance" (docs). First-plot latency (Julia's "time-to-first-plot" compilation cost) is the well-known counterweight.

Strengths ​

  • The reactive model, done natively. Observable + lift/@lift + on make data-driven scenes and live interaction fall out of one primitive; this is the reactive execution model other engines emulate with a ValueTracker.
  • One figure, three backends. The same code exports publication vector graphics (CairoMakie), drives a native interactive window (GLMakie), or runs in a browser (WGLMakie) — a clean CPU-oracle / GPU-preview split.
  • Deterministic vector export. CairoMakie → SVG/PDF is a reproducible archival path independent of GPU drivers.
  • No LaTeX install for math. MathTeXEngine.jl typesets equations in-process; no TeX toolchain, unlike Manim.
  • Extensible via @recipe — custom plot types are backend-agnostic and first-class.
  • Broad 2D/3D scientific vocabulary with GPU acceleration for large data.

Weaknesses ​

  • Not a morph-animation engine. No shape-to-shape Transform, no alignment, no built-in interpolation or easing — the author hand-codes tweens around the record loop.
  • No render cache. No content-hash partial-movie reuse; every frame re-renders.
  • GPU output is not reproducible. GL backends are bitmap and driver-dependent; only CairoMakie is the deterministic oracle.
  • SDF glyphs are monochrome on GL — no emoji/color fonts on GLMakie/WGLMakie (fonts docs).
  • Time-to-first-plot latency — Julia compilation cost on the first render.
  • RPRMakie is experimental — the raytracing backend is not production-stable.

Key design decisions and trade-offs ​

DecisionRationaleTrade-off
Attributes are Observables; animation = mutate + recordOne primitive powers interaction and export; data-driven scenes fall out freeNo timeline/easing/morph layer — author supplies interpolation by hand
One frontend (Makie), swappable backendsSame figure → vector, native GL, or browser; CPU-oracle + GPU-preview from one scriptBackend feature/precision skew (SDF glyphs, bitmap AA) the author must know
CairoMakie = CPU vector reproducible; GL = fast bitmapPublication-quality SVG/PDF vs real-time interaction, each where it's bestGL output not bit-identical; determinism only on the Cairo path
Native MathTeXEngine.jl for math, not a LaTeX toolchainSelf-contained, no TeX install, faster equation typesettingLess complete than a full LaTeX distribution for exotic macros
No per-frame content-hash cacheReactive graph already minimizes intra-frame recompute via lift dependenciesNo cross-run reuse — every record frame re-renders (slower iterate loop)
GL glyphs via signed distance fieldsCheap, scalable, sharp monochrome text on the GPUCannot render color/emoji fonts on GLMakie/WGLMakie
@recipe + convert_arguments extensibility seamThird-party/domain plot types are backend-agnostic and first-classRecipe authors work against Makie's attribute/ComputeGraph model

Sources ​

  • MakieOrg/Makie.jl README.md — positioning ("interactive data visualization and plotting ecosystem"), backend roster, MIT badge.
  • LICENSE.md — "The MIT License (MIT)", Copyright (c) 2018-2021: Simon Danisch, Julius Krumbiegel.
  • docs.makie.org — homepage philosophy ("high performance and extensibility"), "re-exported by each backend package".
  • Observables explanation — Observable container, x[] read/write, on, lift/@lift, the ComputeGraph/Makie.update! (0.24).
  • Observables.jl — the upstream reactive-value package Makie re-exports.
  • Animations explanation — record(fig, path, iter) do i … end, "Animations work by making changes to … Observables and recording the changing figure frame by frame", "Video files are created with FFMPEG_jll.jl".
  • Backends explanation — verbatim one-liners for CairoMakie, GLMakie, WGLMakie, RPRMakie.
  • CairoMakie explanation — SVG/PDF vector output, publication-quality vs GL-bitmap statement, px_per_unit/pt_per_unit, save/activate!(type=…).
  • Scenes explanation — Scene container/tree, scale/translation/ rotation transform, subscenes, "implementation detail for many users".
  • Figure explanation — Figure = top-level Scene + GridLayout + blocks; grid placement fig[1,1] = Axis(fig).
  • Fonts explanation — FreeType.jl loading; GL glyphs are signed distance fields, "can only be used to render monochrome glyphs".
  • Getting started — mutating scatter!/lines! into an axis.
  • Makie/src/ffmpeg-util.jl — VideoStream/VideoStreamOptions, FFMPEG_jll.ffmpeg() pipe, rgb24→yuv420p, libx264/libvpx-vp9, mp4/webm/mkv/gif, framerate 24.
  • Makie/src/recording.jl — record/recordframe! signatures, colorbuffer(screen) readback, save(path, io).
  • JOSS 10.21105/joss.03349 — Danisch & Krumbiegel, "Makie.jl: Flexible high-performance data visualization for Julia" (published Sept 1 2021).
  • FreeTypeAbstraction.jl · MathTeXEngine.jl · FFMPEG_jll — the font-layout, math-typesetting, and encoder dependencies referenced above.