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 ecosystemnot 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 objectsscatter!, 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/colour 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/colour 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 toolchainLaTeXStrings 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: .mp4libx264, .webmlibvpx-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 previewGLMakie 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 samplingCairoMakie'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 cachingN/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/colour 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 colour/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 explanationObservable container, x[] read/write, on, lift/@lift, the ComputeGraph/Makie.update! (0.24).
  • Observables.jl — the upstream reactive-value package Makie re-exports.
  • Animations explanationrecord(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 explanationScene container/tree, scale/translation/ rotation transform, subscenes, "implementation detail for many users".
  • Figure explanationFigure = top-level Scene + GridLayout + blocks; grid placement fig[1,1] = Axis(fig).
  • Fonts explanationFreeType.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.jlVideoStream/VideoStreamOptions, FFMPEG_jll.ffmpeg() pipe, rgb24yuv420p, libx264/libvpx-vp9, mp4/webm/mkv/gif, framerate 24.
  • Makie/src/recording.jlrecord/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.