Plotting with Plots.jl and Makie

Choose a backend, build a figure from series and attributes, lay out subplots, save reliably, and know when Makie is worth the extra weight.

Backends

BackendStrengthNotes
GRDefault, fast, no dependencies beyond a binaryGreat for PNG, SVG and PDF on a server
UnicodePlotsPlots in a terminalUseful over SSH
PlotlyJSInteractive HTMLNeeds a browser or a notebook renderer
PyPlotMatplotlib outputRequires a Python installation
PGFPlotsXPublication LaTeXRequires a LaTeX toolchain
using Plots
gr()                       # chose the backend for this session
default(fmt = :png, size = (800, 500), dpi = 150)

x = range(0, 2pi, length = 200)

p = plot(x, sin.(x);
         label = "sin", lw = 2, color = :steelblue,
         xlabel = "angle (rad)", ylabel = "value",
         title = "Trigonometric functions")

plot!(p, x, cos.(x); label = "cos", ls = :dash, lw = 2)
hline!(p, [0.0]; label = "", color = :grey, alpha = 0.4)

savefig(p, "trig.png")
savefig(p, "trig.svg")
  • plot creates a figure, plot! adds to the current one. The exclamation mark convention means the call mutates rather than returns something new.
  • Always pass an explicit label or label = ""; otherwise the legend fills up with inferred names.
  • The backend is chosen once per session and cannot be swapped after a plot exists.

Layout and series

p1 = scatter(rand(50), rand(50); title = "scatter", markerstrokewidth = 0)
p2 = histogram(randn(1000); bins = 30, title = "histogram", legend = false)
p3 = bar(["a", "b", "c"], [3, 7, 2]; title = "bar")

grid = plot(p1, p2, p3; layout = (2, 2), size = (900, 700))
savefig(grid, "grid.png")

# a 3D surface
xs = ys = range(-2, 2, length = 50)
p4 = surface(xs, ys, (x, y) -> exp(-(x^2 + y^2)))
savefig(p4, "surface.png")

Layouts accept tuples such as (2, 2) or a custom grid with mixed spans through @layout. Build each subplot as a value first, then combine them, rather than chaining everything into one call.

When to use Makie

using CairoMakie        # static, no GPU needed
using GLMakie          # interactive window or browser

fig = Figure(size = (800, 500))
ax = Axis(fig[1, 1], xlabel = "angle (rad)", ylabel = "value", title = "sin")
lines!(ax, x, sin.(x); color = :steelblue, linewidth = 2)
scatter!(ax, x[1:20:end], sin.(x[1:20:end]); color = :tomato)

fig[2, 1] = Legend(fig, ax, "series")
save("makie.png", fig)
💡
On a headless server, Plots with the GR backend and Makie with CairoMakie both render without a display; GLMakie does not. Choose the backend deliberately in a service, or your plotting code will work locally and fail in the container.

FAQ

Plots.jl or Makie?
Plots.jl is quicker to get started and its one API covers many backends. Makie has a more explicit, composable object model, better interactive performance and finer layout control, at the cost of a longer learning curve and heavier dependencies.
Why does savefig produce an empty file?
The figure was never created because a plotting call errored earlier, or a headless session has no display backend. Choose GR or CairoMakie explicitly before the first plotting call.

DataFrames, CSV and tabular data in Julia Debugging, testing and benchmarking Julia code

Last refreshed 2026-09-18.