Makie

A data visualization ecosystem for Julia

Marie-Hélène Burle

October 26, 2022


Introduction

Plotting in Julia

Many options:

  • Plots.jl: high-level API for working with different back-ends (GR, Pyplot, Plotly…)
  • PyPlot.jl: Julia interface to Matplotlib’s matplotlib.pyplot
  • PlotlyJS.jl: Julia interface to plotly.js
  • PlotlyLight.jl: the fastest plotting option in Julia by far, but limited features
  • Gadfly.jl: following the grammar of graphics popularized by Hadley Wickham in R
  • VegaLite.jl: grammar of interactive graphics
  • PGFPlotsX.jl: Julia interface to the PGFPlots LaTeX package
  • UnicodePlots.jl: plots in the terminal 🙂
  • Makie.jl: powerful plotting ecosystem: animation, 3D, GPU optimization

Makie ecosystem

  • Main package:

    • Makie: plots functionalities. Backend needed to render plots into images or vector graphics
  • Backends:

    • CairoMakie: vector graphics or high-quality 2D plots. Creates, but does not display plots (you need an IDE that does or you can use ElectronDisplay.jl)

    • GLMakie: based on OpenGL; 3D rendering and interactivity in GLFW window (no vector graphics)

    • WGLMakie: web version of GLMakie (plots rendered in a browser instead of a window)

Extensions

Cheatsheet 2D

Cheatsheet 3D

Resources

Troubleshooting

Installing GLMakie can be challenging. This page may lead you towards solutions

CairoMakie and WGLMakie should install without issues

Fundamental functioning

Figure

Load the package (here, we are using CairoMakie):

using CairoMakie                        # no need to import Makie itself

Create a Figure (container object):

fig = Figure()


typeof(fig)
Figure

You can customize a Figure:

fig2 = Figure(backgroundcolor=:grey22, resolution=(300, 300))

Makie uses the Colors.jl package as a dependency
You can find a list of all named colours here

To use CSS specification (e.g. hex), you need to install Colors explicitly and use its color parsing capabilities:

using Colors
fig3 = Figure(backgroundcolor=colorant"#adc2eb")

Axis

Then, you can create an Axis:

ax = Axis(Figure()[1, 1])
Axis with 0 plots:


typeof(ax)
Axis
Axis(fig3[1, 1])  # fig3[1, 1] sets the subplot layout: fig[row, col]
fig3
Axis(fig[2, 3])  # This is what happens if we change the layout
fig
Axis(fig3[2, 3])  # We can add another axis on fig3
fig3

Axis are customizable:

fig4 = Figure()
Axis(fig4[1, 1],
     xlabel="x label",
     ylabel="y label",
     title="Title of the plot")
fig4

Plot

Finally, we can add a plot:

fig = Figure()
ax = Axis(fig[1, 1])
x = LinRange(-10, 10, 20)
y = x
scatter!(ax, x, y)  # Functions with ! transform their arguments
fig

Of course, there are many plotting functions, e.g. scatterlines!:

fig = Figure()
ax = Axis(fig[1, 1])
x = LinRange(-10, 10, 20)
y = x
scatterlines!(ax, x, y)  # Functions with ! transform their arguments
fig

We can also use lines!:

fig = Figure()
ax = Axis(fig[1, 1])
x = LinRange(-10, 10, 20)
y = sin.(x)  # The . means that the function is broadcast to each element of x
lines!(ax, x, y)
fig

Let’s add points to get a smoother line:

fig = Figure()
ax = Axis(fig[1, 1])
x = LinRange(-10, 10, 1000)
y = sin.(x)  # The . means that the function is broadcast to each element of x
lines!(ax, x, y)
fig

Now, you don’t have to create the Figure, Axis, and plot one at a time
You can create them at the same time with, for instance lines:

x = LinRange(-10, 10, 1000)
y = sin.(x)
lines(x, y)  # Note the use of lines instead of lines!

Or even more simply:

x = LinRange(-10, 10, 1000)
lines(x, sin)

This is a lot simpler, but it is important to understand the concepts of the Figure and Axis objects as you will need it to customize them:

x = LinRange(-10, 10, 1000)
y = cos.(x)
lines(x, y;
      figure=(; backgroundcolor=:green),
      axis=(; title="Cosinus function", xlabel="x label", ylabel="y label"))

When you create the Figure, Axis, and plot at the same time, you create a FigureAxisPlot object:

x = LinRange(-10, 10, 1000)
y = cos.(x)
obj = lines(x, y;
            figure=(; backgroundcolor=:green),
            axis=(; title="Cosinus function",
                  xlabel="x label",
                  ylabel="y label"));
typeof(obj)
Makie.FigureAxisPlot

Note the ; in the figure and axis value. This is because these are one-element NamedTuples

The mutating functions (with !) can be used to add plots to an existing figure, but first, you need to decompose the FigureAxisPlot object:

fig, ax, plot = lines(x, sin)
lines!(ax, x, cos)  # Remember that we are transforming the Axis object
fig                 # Now we can plot the transformed Figure

Or we can add several plots on different Axis in the same Figure:

fig, ax1, plot = lines(x, sin)
ax2 = Axis(fig[1, 2])
lines!(ax2, x, cos)
fig

Examples

2D

using CairoMakie
using StatsBase, LinearAlgebra
using Interpolations, OnlineStats
using Distributions
CairoMakie.activate!(type = "png")

function eq_hist(matrix; nbins = 256 * 256)
    h_eq = fit(Histogram, vec(matrix), nbins = nbins)
    h_eq = normalize(h_eq, mode = :density)
    cdf = cumsum(h_eq.weights)
    cdf = cdf / cdf[end]
    edg = h_eq.edges[1]
    interp_linear = LinearInterpolation(edg, [cdf..., cdf[end]])
    out = reshape(interp_linear(vec(matrix)), size(matrix))
    return out
end

function getcounts!(h, fn; n = 100)
    for _ in 1:n
        vals = eigvals(fn())
        x0 = real.(vals)
        y0 = imag.(vals)
        fit!(h, zip(x0,y0))
    end
end

m(;a=10rand()-5, b=10rand()-5) = [0 0 0 a; -1 -1 1 0; b 0 0 0; -1 -1 -1 -1]

h = HeatMap(range(-3.5,3.5,length=1200), range(-3.5,3.5, length=1200))
getcounts!(h, m; n=2_000_000)

with_theme(theme_black()) do
    fig = Figure(figure_padding=0,resolution=(600,600))
    ax = Axis(fig[1,1]; aspect = DataAspect())
    heatmap!(ax,-3.5..3.5, -3.5..3.5, eq_hist(h.counts); colormap = :bone_1)
    hidedecorations!(ax)
    hidespines!(ax)
    fig
end

2D

3D

using GLMakie, Random
GLMakie.activate!()

Random.seed!(13)
x = -6:0.5:6
y = -6:0.5:6
z = 6exp.( -(x.^2 .+ y' .^ 2)./4)

box = Rect3(Point3f(-0.5), Vec3f(1))
n = 100
g(x) = x^(1/10)
alphas = [g(x) for x in range(0,1,length=n)]
cmap_alpha = resample_cmap(:linear_worb_100_25_c53_n256, n, alpha = alphas)

with_theme(theme_dark()) do
    fig, ax, = meshscatter(x, y, z;
                           marker=box,
                           markersize = 0.5,
                           color = vec(z),
                           colormap = cmap_alpha,
                           colorrange = (0,6),
                           axis = (;
                                   type = Axis3,
                                   aspect = :data,
                                   azimuth = 7.3,
                                   elevation = 0.189,
            perspectiveness = 0.5),
        figure = (;
            resolution =(1200,800)))
    meshscatter!(ax, x .+ 7, y, z./2;
        markersize = 0.25,
        color = vec(z./2),
        colormap = cmap_alpha,
        colorrange = (0, 6),
        ambient = Vec3f(0.85, 0.85, 0.85),
        backlight = 1.5f0)
    xlims!(-5.5,10)
    ylims!(-5.5,5.5)
    hidedecorations!(ax; grid = false)
    hidespines!(ax)
    fig
end

3D

For more examples, have a look at Beautiful Makie

Compiling sysimages

While Makie is extremely powerful, its compilation time and its time to first plot are extremely long

For this reason, it might save you a lot of time to create a sysimage (a file containing information from a Julia session such as loaded packages, global variables, compiled code, etc.) with PackageCompiler.jl

The upcoming Julia 1.9 will do this automatically

Using the Alliance clusters

CairoMakie

CairoMakie will run without problem on the Alliance clusters

It is not designed for interactivity, so saving to file is what makes the most sense

Example:

save("graph.png", fig)


Remember however that CairoMakie is 2D only (for now)

GLMakie

GLMakie relies on GLFW to create windows with OpenGL

GLFW doesn’t support creating contexts without an associated window

The dependency GLFW.jl will thus not install in the clusters—even with X11 forwarding—unless you use VDI nodes, VNC, or Virtual GL

WGLMakie

You can setup a server with JSServe.jl as per the documentation

However, this method is intended for the creation of interactive widgets, e.g. for a website

While this is really cool, it isn’t optimized for performance

There might also be a way to create an SSH tunnel to your local browser, although there is no documentation on this

Best probably is to save to file

Conclusion: Makie on production clusters

  • 2D plots: use CairoMakie and save to file
  • 3D plots: use WGLMakie and save to file