Advanced Layouts with AlgebraOfGraphics.jl

Authors

Jose Storopoli

Juan Oneto

In this tutorial, we’ll cover some of Makie.jl’s functions to make advanced layouts in AoG.jl. Since AoG.jl uses Makie.jl as a backend most of advanced customizations such as layouts are only possible by interacting directly with Makie.jl.

Note

Feel free to consult Makie.jl’s Documentation.

To begin, like before, let’s load AoG.jl, data wrangling libraries and the DataFrame we’ve used previously:

using PharmaDatasets
using DataFramesMeta

df = dataset("demographics_1")
first(df, 5)
5×6 DataFrame
Row ID AGE WEIGHT SCR ISMALE eGFR
Int64 Float64 Float64 Float64 Int64 Float64
1 1 34.823 38.212 1.1129 0 42.635
2 2 32.765 74.838 0.8846 1 126.0
3 3 35.974 37.303 1.1004 1 48.981
4 4 38.206 32.969 1.1972 1 38.934
5 5 33.559 47.139 1.5924 0 37.198

We will also do some column transformations to CategoricalArrays:

using CategoricalArrays
@transform! df :SEX = categorical(:ISMALE);
@transform! df :SEX = recode(:SEX, 0 => "female", 1 => "male");
@transform! df :WEIGHT_cat = cut(:WEIGHT, 2; labels = ["light", "heavy"])

And now load AoG.jl along with CairoMakie.jl:

using CairoMakie
using AlgebraOfGraphics

1 🎨 Makie.jl Layout System

Makie uses a layout system under the hood that allows you to create visualizations with nested subplots. This works by first creating a Figure object and then adding with one or more Axis or other objects inside it.

Tip

To use the Makie.jl layout system, you just need to import one of its backends. We are already doing that with CairoMakie.jl.

Let’s create an empty Figure:

fig = Figure()

Ok, now that we have an empty Figure, we can specify positions in its attached layout that we can then use to place objects there. Specifying a GridPosition is done by indexing the Figure object by row followed by column in the following syntax:

fig[row, col]

For example, to specify a position in the first row and first column of our previously instantiated Figure object, we can generate the following GridPosition:

fig[1, 1]
GridPosition(GridLayout[1, 1] (0 children), GridLayoutBase.Span(1:1, 1:1), GridLayoutBase.Inner())
Caution

Indexing into a Figure does not retrieve objects placed there, unlike indexing into a matrix. The returned GridPosition object is used as a placeholder to either create new content in the Figure at that position or lookup one or multiple objects with the content or contents functions.

In base Makie.jl, we can use GridPositions to place plots or objects like Axis and Colorbar:

f = Figure()
lines(f[1, 1], 1:0.01:10, sin)
Axis(f[2:3, 1])
Colorbar(f[:, 2])
f

We can do the same thing with complete AoG.jl plots by using the mutating draw!() function.

First, let’s create a Layer object to pass to draw!():

plt = data(df) * mapping(:AGE, :eGFR);

Now we call draw!() with the first argument the Figure with the Axis index we want to modify:

draw!(fig[1, 1], plt)

Now, if we display our original Figure, we can see that it now has exactly one subplot with our visualization:

fig

Tip

This is exactly what AoG.jl does under the hood when we call the draw() (non-mutating) function.

2 🐣 Nested Layouts

This way of instantiating a Figure and an Axis, then calling draw!() on it and then recovering back the original Figure might not appeal to you first as something useful. However, it is quite powerful because we can nest layouts.

See this next example. We will use our original fig, but now we will create a new Layer object:

plt2 = data(df) * mapping(:WEIGHT, :SCR; row = :SEX, col = :WEIGHT_cat);

Now, we **nest fig’s layout by _slicing the Figure’s Axis inside the square brackets []**. We want to have plt2 occupy the first row but the second and third columns of the whole figure:

draw!(fig[1, 2:3], plt2)

Now we display our original fig:

fig

We can keep on adding more stuff if we want to:

plt3 = data(df) * mapping(:AGE; color = :SEX) * AlgebraOfGraphics.density();
draw!(fig[2, 1], plt3)
plt4 = data(df) * mapping(:WEIGHT; color = :SEX) * AlgebraOfGraphics.density();
draw!(fig[2, 2], plt4)
plt5 = data(df) * mapping(:SEX, :AGE) * expectation();
draw!(fig[2, 3], plt5)

And here is the resulting fig:

fig