Backends are the lifeblood of Plots, and the diversity between features, approaches, and strengths/weaknesses was one of the primary reasons that I started this package.

For those who haven't had the pleasure of hacking on 15 different plotting APIs: first, consider yourself lucky. However, you will probably have a hard time choosing the right backend for your task at hand. This document is meant to be a guide and introduction to make that choice.

Persistent backend selection

Plots uses the Preferences mechanism to make the default backend choice persistent across julia restart.

$ JULIA_PKG_PRECOMPILE_AUTO=0 julia -e 'import Plots; Plots.set_default_backend!(:pythonplot)'
$ julia  # restart, show persistent mode
julia> using Plots
[ Info: Precompiling Plots [91a5bcdd-55d7-5caf-9e0b-520d859cae80]
[ Info: PythonPlot  # precompiles for this backend
julia> plot(1:2) |> display  # uses `PythonPlot` by default

You can clear preferences with Plots.set_default_backend!(). Alternatively, one can use the environment variable PLOTS_DEFAULT_BACKEND to select the default backend (but this will need to trigger manual precompilation using Base.compilecache(Plots)).

At a glance

My favorites: GR for speed, Plotly(JS) for interactivity, UnicodePlots for REPL/SSH and PythonPlot otherwise.

If you require...then use...
featuresGR, PythonPlot, Plotly(JS), Gaston
speedGR, UnicodePlots, InspectDR, Gaston
interactivityPythonPlot, Plotly(JS), InspectDR
beautyGR, Plotly(JS), PGFPlots/ PGFPlotsX
REPL plottingUnicodePlots
3D plotsGR, PythonPlot, Plotly(JS), Gaston
a GUI windowGR, PythonPlot, PlotlyJS, Gaston, InspectDR
a small footprintUnicodePlots, Plotly
backend stabilityPythonPlot, Gaston
plot+data -> .hdf5 fileHDF5

Of course this list is rather subjective and nothing in life is that simple. Likely there are subtle tradeoffs between backends, long hidden bugs, and more excitement. Don't be shy to try out something new !


The default backend. Very fast with lots of plot types. Still actively developed and improving daily.


  • Speed
  • 2D and 3D
  • Standalone or inline


  • Limited interactivity

Primary author: Josef Heinen (@jheinen)

Fine tuning

It is possible to use more features of GR via the extra_kwargs mechanism.

using Plots; gr()

x = range(-3, 3, length=30)
  x, x, (x, y)->exp(-x^2 - y^2), c=:viridis, legend=:none,
  nx=50, ny=50, display_option=Plots.GR.OPTION_SHADED_MESH,  # <-- series[:extra_kwargs]

Supported :subplot :extra_kwargs

legend_hfactorVertical spacing factor for legends
legend_wfactorMultiplicative factor influencing the legend width

Supported :series :extra_kwargs

Series TypeKeywordDescription
:surfacenxNumber of interpolation points in the x direction
:surfacenyNumber of interpolation points in the y direction
:surface, :wireframedisplay_optionsee GR doc

Plotly / PlotlyJS

These are treated as separate backends, though they share much of the code and use the Plotly JavaScript API. plotly() is the only dependency-free plotting option, as the required JavaScript is bundled with Plots. It can create inline plots in IJulia, or open standalone browser windows when run from the Julia REPL.

plotlyjs() is the preferred option, and taps into the great functionality of Spencer Lyon's PlotlyJS.jl. Inline IJulia plots can be updated from any cell... something that makes this backend stand out. From the Julia REPL, it taps into Blink.jl and Electron to plot within a standalone GUI window... also very cool. Also, PlotlyJS supports saving the output to more formats than Plotly, such as EPS and PDF, and thus is the recommended version of Plotly for developing publication-quality figures.




  • No custom shapes
  • JSON may limit performance

Primary PlotlyJS.jl author: Spencer Lyon (@spencerlyon2)


Plotly needs to load MathJax to render LaTeX strings, therefore passing extra keywords with extra_kwargs = :plot is implemented. With that it is possible to pass a header to the extra include_mathjax keyword. It has the following options:

  • include_mathjax = "" (default): no mathjax header
  • include_mathjax = "cdn" include the standard online version of the header
  • include_mathjax = "<filename?config=xyz>" include a user-defined file

These can also be passed using the extra_plot_kwargs keyword.

using LaTeXStrings
    [[1,4,9,16]*10000, [0.5, 2, 4.5, 8]],
    labels = [L"\alpha_{1c} = 352 \pm 11 \text{ km s}^{-1}";
              L"\beta_{1c} = 25 \pm 11 \text{ km s}^{-1}"] |> permutedims,
    xlabel = L"\sqrt{(n_\text{c}(t|{T_\text{early}}))}",
    ylabel = L"d, r \text{ (solar radius)}",
    yformatter = :plain,
    extra_plot_kwargs = KW(
        :include_mathjax => "cdn",
        :yaxis => KW(:automargin => true),
        :xaxis => KW(:domain => "auto")

Fine tuning

It is possible to add additional arguments to the plotly series and layout dictionaries via the extra_kwargs mechanism. Arbitrary arguments are supported but one needs to be careful since no checks are performed and thus it is possible to unintentionally overwrite existing entries.

For example adding customdata can be done the following way scatter(1:3, customdata=["a", "b", "c"]). One can also pass multiple extra arguments to plotly.

pl = scatter(
    extra_kwargs = KW(
        :series => KW(:customdata => ["a", "b", "c"]),
        :plot => KW(:legend => KW(:itemsizing => "constant"))


A Julia wrapper around the popular python package Matplotlib. It uses PythonCall.jl to pass data with minimal overhead.

2023-08-30T09:40:46.800513 image/svg+xml Matplotlib v3.7.2,