Here is a quick tutorial to get you started. We assume you have Julia and
GLMakie.jl (or one of the other backends) installed already.
First, we import GLMakie, which might take a little bit of time because there is a lot to precompile. Just sit tight! For this tutorial, we also call
AbstractPlotting.inline!(true) so plots appear inline after each example. Otherwise, an interactive window will open when you return a
using GLMakie AbstractPlotting.inline!(true)
Figure is usually displayed whenever it is returned in global scope (e.g. in the REPL). To display a Figure from within a local scope, like from within a function, you can directly call
Let's begin by plotting some points using the
points = [Point2f0(cos(t), sin(t)) for t in LinRange(0, 2pi, 20)] colors = 1:20 figure, axis, scatterobject = scatter(points, color = colors, markersize = 15) figure
You can see that we've split the return value of
scatter into three components:
scatterobject. Every plotting function in its default form returns an object of type
FigureAxisPlot which bundles these three parts, which makes it easy to continue working separately with them.
One great feature of Makie is that it uses
Nodes as a Makie-specific alias), which make it easy to write visualizations that can be updated dynamically with new data.
Observable is a container object which notifies all its listeners whenever its content changes. Put simply, using
Observables, if your input data changes your plots change as well.
Plot objects usually have a collection of attributes, which are observables. If you change them, the plots update immediately. Let's try to change the marker size of our scatter plot:
scatterobject.markersize = 30 figure
Let's add another scatter plot to our axis. To add a plot to an existing figure or axis, you use the mutating version with a
!. Each plot type such as
Scatter has a non-mutating function (
scatter) and a mutating function (
scatter!) associated with it.
Let's plot another circle. This time we try some different arguments, a circle function and a range of values.
scatter! without passing a specific target as the first argument, which plots into the last used axis.
circlefunc = ts -> 1.5 .* Point2f0.(cos.(ts), sin.(ts)) scatter!(circlefunc, LinRange(0, 2pi, 30), color = :red) figure
So far, we have plotted normal "static" values - a simple array of points, or a function evaluated on static values. Makie makes it really easy to plot "dynamic" values as well. This is done using Observables.
Imagine that you want to interactively visualize how a sine function over a constant interval depends on its parameters. That means the x values are fixed but the y values depend on the frequency and phase of the sine function. Such a dependency is easy to express with
Nodes for short.
Usually, all plot functions accept their input arguments and attributes as
Observables. If you don't pass
Observables, they get converted internally anyway.
xs = -pi:0.01:pi frequency = Node(3.0) # Node === Observable phase = Node(0.0) ys = lift(frequency, phase) do fr, ph @. 0.3 * sin(fr * xs - ph) end lines!(xs, ys, color = :blue, linewidth = 3) figure
You can see that our sine function was nicely visualized. The
lift function takes as its first input a function which computes its output from the other arguments,
phase in this case, which are
Observables. The output is then stored inside another
ys always contains the result of the sine function with the current
phase applied to the values in
xs. (If you haven't used the
do syntax before, it is Julia's way of passing an anonymous function as the first argument to another function. It's very useful for dealing with
For short functions, there is a really convenient macro alternative to
lift. Instead of what we wrote above, we could have written
ys = @lift(0.3 * sin($frequency .* xs .- $phase)). Just prefix expressions that reference observables with a
Now, we can change the
frequency to a different value and the plot will change with it.
Observables are mutated with empty square brackets (like
frequency = 9 figure
You see that the line plot has changed to reflect the new frequency. That's how easy it is to create a dynamic visualization with
Observables. Imagine the opportunities to hook Observables up with sliders and buttons to control a complex plot.
Makie overloads the
FileIO interface. This is how you save this figure as a
Different backends have different possible output formats.
GLMakie as a GPU-powered backend can only output bitmaps like
CairoMakie can output high-quality vector graphics such as
See Output for more information on this.
Often, we want to create small videos that show how a visualization changes over time. This is really easy to do if we already have a plot with observables. Once we have our figure, we can just change the observables that we want in a closure function and pass that to
record, which creates a video for us.
We can just re-use our existing figure. Let's change the phase over time. We just need to supply an iterator with as many elements as we want frames in our video.
framerate = 30 # fps timestamps = 0:1/framerate:3 record(figure, "phase_animation.mp4", timestamps; framerate = framerate) do t phase = 2 * t * 2pi end
And here is our result, as we expect the sine function moves sideways.
That concludes our short tutorial. We hope you have learned how to create basic plots with Makie and how easy it is to change and animate them using Observables.
You can check out more examples that you can adapt in the Example Gallery.