Here is a quick tutorial to get you started with Makie!
We assume you have Julia and
CairoMakie.jl (or one of the other backends, i.e.
WGLMakie.jl) installed already.
This tutorial uses CairoMakie, but the code can be executed with any backend. CairoMakie can output beautiful static vector graphics, but it doesn't have the native ability to open interactive windows.
To see the output of plotting commands when using CairoMakie, we recommend you either use an IDE which supports png or svg output, such as VSCode, Atom/Juno, Jupyter, Pluto, etc., or try using a viewer package such as ElectronDisplay.jl, or alternatively save your plots to files directly. The Julia REPL by itself does not have the ability to show plots.
GLMakie can open interactive windows, or alternatively display bitmaps inline if
Makie.inline!(true) is called.
WGLMakie shows interactive plots in environments that support interactive html displays, such as VSCode, Atom/Juno, Jupyter, Pluto, etc.
For more information, have a look at Backends & Output.
Ok, now that this is out of the way, let's get started!
First, we import CairoMakie.
Makie has many different plotting functions, one of the most common ones is lines. You can just call such a function and your plot will appear if your coding environment can show png or svg files.
Objects such as Figure,
Scene are usually displayed whenever they are returned in global scope (e.g. in the REPL). To display such objects from within a local scope, like from within a function, you can directly call
display(figure), for example.
x = range(0, 10, length=100) y = sin.(x) lines(x, y)
Another common function is scatter.
using CairoMakie x = range(0, 10, length=100) y = sin.(x) scatter(x, y)
Every plotting function has a version with and one without
!. For example, there's
lines!, etc. The functions without a
! always create a new axis with a plot inside, while the functions with
! plot into an already existing axis.
Here's how you could plot two lines on top of each other.
using CairoMakie x = range(0, 10, length=100) y1 = sin.(x) y2 = cos.(x) lines(x, y1) lines!(x, y2) current_figure()
lines! call plots into the axis created by the first
lines call. If you don't specify an axis to plot into, it's as if you had called
The call to
current_figure is necessary here, because functions with
! return only the newly created plot object, but this alone does not cause the figure to display when returned.
Every plotting function has attributes which you can set through keyword arguments. The lines in the previous example both have the same default color, which we can change easily.
using CairoMakie x = range(0, 10, length=100) y1 = sin.(x) y2 = cos.(x) lines(x, y1, color = :red) lines!(x, y2, color = :blue) current_figure()
Other plotting functions have different attributes. The function
scatter, for example, does not only have the
color attribute, but also a
using CairoMakie x = range(0, 10, length=100) y1 = sin.(x) y2 = cos.(x) scatter(x, y1, color = :red, markersize = 5) scatter!(x, y2, color = :blue, markersize = 10) current_figure()
If you save the plot object returned from a call like
scatter!, you can also manipulate its attributes later with the syntax
plot.attribute = new_value.
using CairoMakie x = range(0, 10, length=100) y1 = sin.(x) y2 = cos.(x) scatter(x, y1, color = :red, markersize = 5) sc = scatter!(x, y2, color = :blue, markersize = 10) sc.color = :green sc.markersize = 20 current_figure()
A lot of attributes can be set to either a single value or an array with as many elements as there are data points. For example, it is usually much more performant to draw many points with one scatter object, than to create many scatter objects with one point each.
Here are the two scatter plots again, but one has varying markersize, and the other varying color.
using CairoMakie x = range(0, 10, length=100) y1 = sin.(x) y2 = cos.(x) scatter(x, y1, color = :red, markersize = range(5, 15, length=100)) sc = scatter!(x, y2, color = range(0, 1, length=100), colormap = :thermal) current_figure()
Note that the color array does not actually contain colors, rather the numerical values are mapped to the plot's
colormap. There are many different colormaps to choose from, take a look on the Colors page.
The values are mapped to colors via the
colorrange attribute, which by default goes from the minimum to the maximum color value, but we can also limit or expand the range manually. For example, we can constrain the previous scatter plot's color range to (0.25, 0.75), which will clip the colors at the bottom and the top quarters.
sc.colorrange = (0.25, 0.75) current_figure()
Of course you can also use an array of colors directly, in which case the
colorrange is ignored:
using CairoMakie x = range(0, 10, length=100) y = sin.(x) colors = repeat([:crimson, :dodgerblue, :slateblue1, :sienna1, :orchid1], 20) scatter(x, y, color = colors, markersize = 20)
If you add label attributes to your plots, you can call the
axislegend function to add a legend with all labeled plots to the current axis.
using CairoMakie x = range(0, 10, length=100) y1 = sin.(x) y2 = cos.(x) lines(x, y1, color = :red, label = "sin") lines!(x, y2, color = :blue, label = "cos") axislegend() current_figure()
Makie uses a powerful layout system under the hood, which allows you to create very complex figures with many subplots. For the easiest way to do this, we need a Figure object. So far, we haven't seen this explicitly, it was created in the background in the first plotting function call.
We can also create a Figure directly and then continue working with it. We can make subplots by giving the location of the subplot in our layout grid as the first argument to our plotting function. The basic syntax for specifying the location in a figure is
using CairoMakie x = LinRange(0, 10, 100) y = sin.(x) fig = Figure() lines(fig[1, 1], x, y, color = :red) lines(fig[1, 2], x, y, color = :blue) lines(fig[2, 1:2], x, y, color = :green) fig
lines call creates a new axis in the position given as the first argument, that's why we use
lines and not
Like Figures, we can also create axes manually. This is useful if we want to prepare an empty axis to then plot into it later.
The default 2D axis that we have created implicitly so far is called Axis and can also be created in a specific position in the figure by passing that position as the first argument.
For example, we can create a figure with three axes.
using CairoMakie fig = Figure() ax1 = Axis(fig[1, 1]) ax2 = Axis(fig[1, 2]) ax3 = Axis(fig[2, 1:2]) fig
And then we can continue to plot into these empty axes.
lines!(ax1, 0..10, sin) lines!(ax2, 0..10, cos) lines!(ax3, 0..10, sqrt) fig
Note, the notation
0..10 above creates a closed interval from
IntervalSets.jl for further details).
Axes also have many attributes that you can set, for example to give them a title, or labels.
ax1.title = "sin" ax2.title = "cos" ax3.title = "sqrt" ax1.ylabel = "amplitude" ax3.ylabel = "amplitude" ax3.xlabel = "time" fig
We have seen two
Layoutables so far, the Axis and the Legend which was created by the function
Layoutables can be placed into the layout of a figure at arbitrary positions, which makes it easy to assemble complex figures.
In the same way as with the Axis before, you can also create a Legend manually and then place it freely, wherever you want, in the figure. There are multiple ways to create Legends, for one of them you pass one vector of plot objects and one vector of label strings.
You can see here that we can deconstruct the return value from the two
lines calls into one newly created axis and one plot object each. We can then feed the plot objects to the legend constructor. We place the legend in the second column and across both rows, which centers it nicely next to the two axes.
using CairoMakie fig = Figure() ax1, l1 = lines(fig[1, 1], 0..10, sin, color = :red) ax2, l2 = lines(fig[2, 1], 0..10, cos, color = :blue) Legend(fig[1:2, 2], [l1, l2], ["sin", "cos"]) fig
The Colorbar works in a very similar way. We just need to pass a position in the figure to it, and one plot object. In this example, we use a
You can see here that we split the return value of
heatmap into three parts: the newly created figure, the axis and the heatmap plot object. This is useful as we can then continue with the figure
fig and the heatmap
hm which we need for the colorbar.
using CairoMakie fig, ax, hm = heatmap(randn(20, 20)) Colorbar(fig[1, 2], hm) fig
The previous short syntax is basically equivalent to this longer, manual version. You can switch between those workflows however you please.
using CairoMakie fig = Figure() ax = Axis(fig[1, 1]) hm = heatmap!(ax, randn(20, 20)) Colorbar(fig[1, 2], hm) fig