Plots.jl and the Backend System
Plots.jl is not itself a rendering engine — it is a unified, consistent plotting API that delegates the actual drawing work to a swappable 'backend' package, most commonly GR (the fast default, good for large datasets and quick iteration), PyPlot (a wrapper around Python's matplotlib), or PlotlyJS (interactive, browser-based plots with zoom and hover tooltips). You select a backend by calling its activation function once, such as gr() or plotlyjs(), after which every subsequent plot() call in your session uses that backend, meaning the same plotting code can produce a fast static PNG one day and an interactive HTML chart the next simply by switching the active backend.
Cricket analogy: A commentary team that can broadcast the exact same match feed through either a fast radio call or a slower, richly produced television package uses different 'backends' for the same underlying event — Plots.jl's gr() versus plotlyjs() is that same choice of delivery mechanism for identical plotting code.
Basic Plotting Commands
The core function is plot(x, y; kwargs...), which creates a new figure, while plot!(x, y; kwargs...) — note the bang — mutates the current figure in place by adding a new series to it rather than starting a fresh plot, a convention Julia uses consistently for any function that modifies its argument. Related shorthand functions like scatter(), bar(), histogram(), and heatmap() call plot() internally with seriestype set appropriately, and any of them accepts common attributes as keyword arguments such as label, xlabel, ylabel, title, linewidth, and color, which apply uniformly across every backend even though the backends render them differently under the hood.
Cricket analogy: Starting a fresh scorecard for a new innings is like plot(), while adding another batter's partnership onto the same existing scorecard rather than starting over is exactly what plot!() does — the bang signals 'add to what's already there'.
using Plots
gr() # activate the GR backend (fast, static rendering)
x = 0:0.1:2π
y1 = sin.(x)
y2 = cos.(x)
plot(x, y1, label="sin(x)", xlabel="x", ylabel="y", title="Trig Functions", linewidth=2, color=:blue)
plot!(x, y2, label="cos(x)", linewidth=2, color=:red, linestyle=:dash) # adds to the same figure
scatter(x[1:10:end], y1[1:10:end], label="sampled points", markersize=6)Customizing Plots with Attributes
Plots.jl exposes a large, consistent attribute vocabulary — legend=:topright, xlims=(0, 10), yscale=:log10, seriestype=:bar, and dozens more — that works identically regardless of which backend renders the figure, so learning the attribute names once transfers across every backend you might switch to later. Attributes can be set globally for the whole session with default(fontfamily="Computer Modern", linewidth=2), per-plot by passing them to plot(), or per-series when using plot!() to layer multiple series with different styling onto the same axes, giving fine control without needing backend-specific code anywhere in your script.
Cricket analogy: A cricket board setting a standard pitch report format used identically at every stadium in the league, whether played in Mumbai or Chennai, mirrors Plots.jl's attribute vocabulary working identically no matter which backend renders the final chart.
Because plot!() mutates the currently active figure, calling it after switching contexts (e.g., inside a loop where you meant to create a new plot each iteration) can silently keep layering series onto an old figure. If you want a fresh figure each time, call plot() (no bang) explicitly, or store figure handles in variables — e.g., p = plot(x, y) then plot!(p, x2, y2) — to be unambiguous about which figure you're modifying.
Layouts and Subplots
Multiple subplots are arranged in a single figure using the layout keyword, either as a simple grid like layout=(2,2) for a 2-row, 2-column arrangement, or via the more expressive @layout macro for irregular grids, such as @layout [a{0.3h}; b c] which creates one wide panel on top and two narrower panels below with a specific height ratio. Each individual subplot in the grid can be targeted for further customization with plot!(p, subplot=i, ...), and this layout system composes naturally with the same plot()/plot!() and attribute vocabulary used for single-panel figures, so nothing new needs to be learned beyond the layout specification itself.
Cricket analogy: A stadium's giant screen split into a 2x2 grid showing the main match feed, a replay angle, the scorecard, and player stats simultaneously mirrors layout=(2,2) arranging four subplots in one figure, each independently customizable.
using Plots
gr()
p1 = plot(sin, 0, 2π, title="sin")
p2 = plot(cos, 0, 2π, title="cos")
p3 = histogram(randn(1000), title="Normal samples")
p4 = scatter(rand(50), rand(50), title="Random scatter")
# Simple 2x2 grid
plot(p1, p2, p3, p4, layout=(2, 2), legend=false)
# Irregular layout: one wide panel on top, two below
l = @layout [a{0.3h}; b c]
plot(p1, p2, p3, layout=l)
savefig("my_dashboard.png")
savefig("my_dashboard.pdf") # vector format, good for print/publicationSaving and Exporting Plots
Any figure can be written to disk with savefig("filename.ext"), and Plots.jl infers the output format entirely from the file extension — .png and .svg work with essentially every backend, .pdf gives a scalable vector format well suited for print or publication, and interactive backends like PlotlyJS can export standalone .html files that retain zoom and hover interactivity even when opened outside of Julia. For high-resolution raster output intended for print or presentation slides, passing dpi=300 to plot() (or setting it via the size and dpi keyword combination) avoids the blurry, pixelated look that a default-resolution PNG produces when scaled up.
Cricket analogy: A broadcaster exporting the day's highlights as a quick low-res clip for social media versus a full broadcast-quality master for the archive is choosing an export format the same way savefig()'s extension choice determines a plot's output quality and interactivity.
Interactive exploration tip: activate plotlyjs() while working in a Jupyter notebook or Pluto.jl to get zoomable, hoverable charts during analysis, then switch to gr() before generating final static figures for a paper or report — the exact same plotting code works unchanged with either backend.
- Plots.jl is a unified plotting API; the actual rendering is delegated to a swappable backend like GR, PyPlot, or PlotlyJS, activated with gr(), pyplot(), or plotlyjs().
- plot() creates a new figure; plot!() (with a bang) mutates the current figure by adding a new series — Julia's standard convention for in-place mutation.
- scatter(), bar(), histogram(), and similar functions are shorthands that call plot() with an appropriate seriestype.
- A consistent attribute vocabulary (label, xlabel, legend, yscale, linewidth, color, etc.) works identically across every backend.
- layout=(rows, cols) or the @layout macro arranges multiple subplots in one figure, including irregular, proportionally-sized panels.
- savefig("file.ext") infers the output format from the file extension; use dpi=300 for print-quality raster output.
- PlotlyJS exports standalone interactive .html files that retain zoom and hover functionality outside of Julia.
Practice what you learned
1. What role does a 'backend' play in Plots.jl?
2. What is the difference between plot() and plot!()?
3. How do you arrange four separate plots into a single figure with two rows and two columns?
4. How does savefig() determine what image format to write?
5. What is a practical benefit of using plotlyjs() during exploratory analysis in a notebook?
Was this page helpful?
You May Also Like
DataFrames.jl Basics
An introduction to tabular data manipulation in Julia with DataFrames.jl — creating, inspecting, filtering, grouping, and joining data tables.
The Julia Package Manager (Pkg)
How to use Julia's built-in Pkg to create reproducible environments, add and version packages, and understand Project.toml and Manifest.toml.
Performance Tips in Julia
Practical techniques for writing fast, type-stable Julia code, from avoiding global variables to benchmarking with BenchmarkTools.jl.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics