# Customization of `AlgebraOfGraphics.jl` Plots

Authors

Jose Storopoli

Juan Oneto

In this tutorial, we’ll explore different ways to customize your plots with `AoG.jl`. Some of these customizations will be directed towards `Makie.jl`, `AoG.jl`’s backend.

First, we’ll learn more about the syntax that we can use inside the `mapping()` function, along with some handy `AoG.jl`’s helper functions that can be also used inside `mapping()`. To conclude, we’ll take a dive into in-depth customizations of the following features:

1. `Axis`: subplots inside the plot.
2. `Figure`: general plot customizations.
3. `Legend`: legend positioning and customizations.
4. `Colorbar`: tweak colorbar’s position and appearance.
Note

We’ll cover advanced layouts with `Axis` in Tutorial 7 - Advanced Layouts with `AlgebraOfGraphics.jl`. Don’t forget to check it out.

## 1 🗺️ The `mapping()` syntax

`AoG.jl` uses a similar pair syntax that `DataFrames.jl` and `DataFramesMeta.jl` uses. As we’ve seen in Manipulating Tables with `DataFramesMeta.jl` of the Data Wrangling in Julia Tutorials, this syntax boils down to three variants:

1. `:column_name => "My Custom New Label"`: This is what we call a noop (no operation), it simply renames a column.
2. `:column_name => function()`: This is an operation being applied to the column, but with no renaming.
3. `column_name => function() => "My Custom New Label"`: This is a mix of the previous two, i.e. an operation applied to a column along with renaming.
Tip

The `function()` inside the `mapping()` pair specification must have either zero arguments and no parentheses, such as `:col => mean` for example; or, in the case of arguments, be an anonymous function, e.g. `col => (x -> round(x; digits = 2))`. For more on Julia functions, don’t forget to check our Data Wrangling Tutorials, specifically Functions.

To begin, like before, let’s load `AoG.jl`, the 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 columns transformations to `CategoricalArray`s:

``````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``````

Now that everything is loaded, let’s show some examples with the pair `mapping()` syntax.

First, a simple renaming of a column:

``data(df) * mapping(:AGE => "Age in Years") * histogram() |> draw``

It also works for keyword arguments inside `mapping()`:

``data(df) * mapping(:AGE; layout = :SEX => "Sex") * histogram() |> draw``

Also works for `x` and `y` axes positional arguments:

``````data(df) *
mapping(:AGE => "Age in Years", :eGFR => "Estimated Glomerular Filtration Rate") |> draw``````

Now let’s us apply some operations inside `mapping()`:

``````# Age in Months
data(df) * mapping(:AGE => (x -> x * 12)) * histogram() |> draw``````

The last plot works best with also a renaming:

``````# Much Better
data(df) * mapping(:AGE => (x -> x * 12) => "Age in Months") * histogram() |> draw``````

Here is a more advanced example where we are calculating the ratio for `:AGE` / `:WEIGHT`, just to show that you can stick a function with two or more column arguments inside `mapping()`. Just pass the columns as a tuple (between parentheses):

``````# a simple function just to showcase
ratio(x, y) = x / y``````
``ratio (generic function with 1 method)``
``data(df) * mapping((:AGE, :WEIGHT) => ratio => "My Ratio") * histogram() |> draw``

## 2 🦮 Helper functions

`AoG.jl` has some helper functions provided for the most data wrangling stuff you need to do inside a `mapping()` call within the pair syntax. They are:

• `renamer()`: rename unique values.
• `sorter()`: sort unique values.
• `nonnumeric()`: treat values as categorial (i.e. factors or discrete variables)

Let’s cover `renamer()` first. We use `renamer()` whenever we want to rename values of a categorical column. It works right out of the bat inside a `mapping()` call using the pair syntax.

Let’s make a simple change in the unique values of the column `:WEIGHT_cat`:

``````data(df) *
mapping(
:AGE;
layout = :WEIGHT_cat => renamer("light" => "Underweight", "heavy" => "Overweight"),
) *
histogram() |> draw``````

`renamer` accepts either a sequence or a vector of pairs in the following convention:

``"old value" => "new value"``

`sorter()` works by passing either a sequence or vector of strings which will be used to reorder the unique values in the desired column. The ordering will be done by following the sequence of arguments inside `sorter()`.

In a histogram above that we plotted using `:AGE` as the variable and facetting with `:SEX`, the first value (to the right) was `female` and the second (to the left) was `male`. We can change that with `sorter()`:

``````data(df) *
mapping(:AGE; layout = :SEX => sorter("male", "female") => "Gender") *
histogram() |> draw``````

We could also have passed a vector of strings:

``sorter(["male", "female"])``

Sometimes, our dataset has integers to specify categories. `AoG.jl` will probably error on most of the operations that expects a categorical variable (`String` or `CategoricalArray`) as an input column.

For example, using the column `:ISMALE` which is filled with either `0` or `1` (integer types) fails if you use it in a “categorical” setting:

``data(df) * mapping(:AGE; layout = :ISMALE) * histogram() |> draw``
``MethodError: MethodError(Core.kwcall, ((layout = [0, 1, 1, 1, 0, 0, 0, 0, 0, 1  …  1, 1, 0, 0, 1, 1, 1, 0, 1, 0], datalimits = ((19.187, 79.292),), closed = :left, normalization = :none), AlgebraOfGraphics._histogram, ([34.823, 32.765, 35.974, 38.206, 33.559, 53.758, 25.306, 39.897, 54.975, 40.732  …  30.317, 44.131, 20.009, 69.412, 43.751, 31.245, 45.48, 61.124, 33.803, 40.145],)), 0xffffffffffffffff)``

To fix that you need to pass the `nonnumeric()` helper function. `nonnumeric()` tells `AoG.jl` that despite the column being “numeric” we do not want to use it in a numeric fashion, but instead in a discrete/categorical fashion. It takes no arguments and you can easily insert in a `mapping()` call within the pair syntax:

``````data(df) *
mapping(:AGE; layout = :ISMALE => nonnumeric) * # now it works!
histogram() |> draw``````

## 3 📈 `Axis` customization

Now we will delve into the thin line between `AoG.jl` and its backend `Makie.jl`. Since `AoG.jl` uses `Makie.jl` as a backend, some of the finer details we can only customize using `Makie.jl` keywords arguments. One of such finer customizations is the `Axis` customization, that controls the majority of customizations available in a plot.

Caution

Remember that pipe syntax that we were using almost exclusively to plot our `AoG.jl` visualizations? Let us refresh your memory. This one:

``data(...) * mapping(...) * visual(...) |> draw``

All of the customizations that we will cover in the remainder of this tutorial will need to be passed as keyword arguments to `draw()`. So this makes the piping operation to `draw()` inconvenient. Thus, we will be doing the following syntax now:

``````plt = data(...) * mapping(...) * visual(...)
draw(plt; ...)``````

We’ll define a `AoG.jl` `Layer` object and assign it to `plt` or `plt_something` and then afterwards we’ll call `draw()` on that `Layer` object.

To pass keyword arguments to customize `Axis`’ attributes, you need to pass a `NamedTuple` of the desired keyword arguments to `draw()` via:

``draw(...; axis = (; keyword_1 = value_1, keyword_2 = value_2))``
Caution

Note that we used the `NamedTuple` syntax with a leading semicolon to pass axis keywords, this is useful because it protects us from a common bug. That bug happens if you only want to pass one keyword, `axis = (; a = 1)` creates a `NamedTuple` but `axis = (a = 1)` creates a local variable `a` and your code will fail with a cryptic error. (For multi-element `NamedTuples`, however, it is allowed to leave out the semicolon.)

Let us define first a base plot for us to customize:

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

The first thing we’ll cover in `Axis` tweaking is the `aspect` ratio of our plots. If you want “instagram” like plots you can fix your aspect ratio to 1:

``draw(plt; axis = (; aspect = 1))``
Tip

One worthy mention is the `DataAspect()` which makes all plots in your `AoG.jl` visualization have the same aspect ratio as their x/y limits (this means the same distance along each axis is equivalent to the same distance in data space):

``draw(plt; axis = (; aspect = DataAspect()))``

If you want a title to be on your plot you can do so with `title` as a keyword argument to `Axis`:

``draw(plt; axis = (; title = "My Plot"))``

There’s also `titlealign`, `titlecolor`, `titlefont`, `titlegap` and `titlesize`:

``````draw(
plt;
axis = (;
title = "My Plot",
titlealign = :left, # using Makie.jl's symbols
titlecolor = :blue, # using Makie.jl's symbols
titlefont = "DejaVu Sans Mono",
titlegap = 30,      # the gap between title and the plot
titlesize = 32,     # title's font size
),
)``````

Sometimes we want to override the default resolution of the x- or y-axis ticks. This can be done with `xticks` and `yticks` `Axis`’ keyword arguments. It accepts a vector or a range:

``draw(plt; axis = (; xticks = 20:5:80))``

This is similar to `ggplot2`’s `xlim()`, `ylim()` and `lims()`. `limits` takes a tuple with 2 elements:

1. x-axis limits: another tuple of `(lower, upper)`.
2. y-axis limits: another tuple of `(lower, upper)`.

So, if you want to override x-axis limits to something between `0` and `2` and the y-axis limits to something between `10` and `20`, you can with:

``draw(...; axis = (; limits = ((0, 2), (10, 20))))``
Caution

Watch out! This is a “nested” tuple: a tuple inside a tuple.

If you only want to specify either one of the x- or y-axis you can just input `nothing` to leave it unchanged. Thus, to only alter the y-axis limits you can use `nothing` as the first value in the tuple to represent the x-axis unchanged limits, and then you can pass a tuple which represents the y-axis limits:

``draw(...; axis = (; limits = (nothing, (10, 20))))``

Here’s an example:

``draw(plt; axis = (; limits = ((30, 60), (50, 100))))``

If you don’t want to have grids in your visualizations you can disable then with `xgridvisible` and `ygridvisible`:

``draw(plt; axis = (; xgridvisible = false, ygridvisible = false))``

This is analogous to `ggplot2`’s `scale_[x|y]_log10()`. You can specify `AoG.jl` to use log10 scale in either x- or y-axis with:

``draw(plt; axis = (; [x | y]scale = log10))``

For example:

``draw(plt; axis = (; xscale = log10, yscale = log10))``

## 4 🖼️ `Figure` customizations

The next finer customizations available in the interface between `AoG.jl` and `Makie.jl` is the `Figure` customizations. These are customizations that impact the whole visualization “figure”.

To pass keyword arguments to customize `Figure`’s attributes, you need to pass a `NamedTuple` of the desired keyword arguments to `draw()` via:

``draw(...; figure = (; keyword_1 = value_1, keyword_2 = value_2))``

The first `Figure` customization is the `resolution`. This is probably the most used `Figure` customization. It accepts a tuple with 2 elements. The first element is the `Figure`’s width in pixels. And the second element is the `Figure`’s height in pixels.

So, for example a 600x400 custom visualization can be specified as:

``draw(plt; figure = (; resolution = (600, 400)))``

Or an Ultra HD 1920x1080 visualization:

``draw(plt; figure = (; resolution = (1920, 1080)))``
Tip

If you specify a high resolution like 1920x1080, you’ll probably need to scale the plot attributes up accordingly. However, you can also simply scale up the output resolution of bitmap saved from the same figure by specifying a `px_per_unit` value higher than 1.

For example you can save a 1920x1080 pixel bitmap from a 960x540 figure `fig` by doing `save(fig, px_per_unit = 2)`. You can also set this value for all inline output with `CairoMakie.activate!(px_per_unit = 2)`.

We can increase or decrease the padding of a `Figure` with `figure_padding`:

``draw(plt; figure = (; figure_padding = 0)) # NO padding``
``draw(plt; figure = (; figure_padding = 100)) # a LOT of padding``

By default, a visualization’s background color is always white. You can change this with `backgroundcolor`:

``draw(plt; figure = (; backgroundcolor = :gray80)) # light gray``

## 5`Legend` customizations

Another way to fine tune your visualizations is to tweak the legend. This is also accomplished by the interface between `AoG.jl` and `Makie.jl`.

To pass keyword arguments to customize `Legend`’s attributes, you need to pass a `NamedTuple` of the desired keyword arguments to `draw()` via:

``draw(...; legend = (; keyword_1 = value_1, keyword_2 = value_2))``

First, we need a new `Layer` `AoG.jl` object with a legend:

``plt_legend = data(df) * mapping(:AGE, :eGFR; color = :SEX);``

The first keyword argument that we’ll cover in legend tweaking is the `position`. By default it has the `:right` as value. We can specify some other `Makie.jl` symbols:

• `:top`.
• `:bottom`.
• `:left`.
• `:right`.
``draw(plt_legend; legend = (; position = :top))``

Next, we can also tweak the legend’s title position with the `titleposition` keyword argument with the same symbols as before:

``draw(plt_legend; legend = (; position = :top, titleposition = :left))``

We can also remove the frame that borders the legend with `framevisible=false`:

``draw(plt_legend; legend = (; position = :top, titleposition = :left, framevisible = false))``

Finally, like `Figure`, we can also increase or decrease the padding of a legend with `padding`:

``````draw(
plt_legend;
legend = (; position = :top, titleposition = :left, framevisible = true, padding = 0), # NO padding!
)``````
``````draw(
plt_legend;
legend = (; position = :top, titleposition = :left, framevisible = true, padding = 50), # LOTS of padding!
)``````

## 6 🖌️ `Colorbar` customizations

Our final fine tune customization is the `Colorbar` customizations. These are controlled with the `colorbar` keyword argument inside `draw()`. To pass keyword arguments to customize `Colorbar`’s attributes, you need to pass a `NamedTuple` of the desired keyword arguments to `draw()` via:

``draw(...; colorbar = (; keyword_1 = value_1, keyword_2 = value_2))``
Tip

Don’t forget to check out `Makie.jl`’s documentation on `Colorbar` to find more in-depth examples and use cases.

First, we need a new `Layer` `AoG.jl` object with a colorbar:

``plt_colorbar = data(df) * mapping(:AGE, :WEIGHT; color = :eGFR);``

And here’s our “base” visualization with a colorbar:

``draw(plt_colorbar)``

The first tweak that we can do in a colorbar is with respect to its positioning. The **keyword argument is `position` and it takes the same `Makie.jl` symbols we saw above:

• `:top`.
• `:bottom`.
• `:left`.
• `:right` (default).
``draw(plt_colorbar; colorbar = (; position = :top))``

We can also customize the size of a colorbar (the size is height for horizontal and width for vertical colorbars). You can alternatively set `height` and `width` directly:

``draw(plt_colorbar; colorbar = (; position = :top, size = 30))``
``draw(plt_colorbar; colorbar = (; position = :top, height = 10, width = Relative(0.5)))``

This is similar to the ticks in an axis. You can also specify which ticks are visible in a colorbar with the `ticks` keyword.

Our previous colorbar visualizations had ticks at multiples of `25`, starting from `25` to `125`. For example, we can tweak that to multiples of `10`:

``draw(plt_colorbar; colorbar = (; position = :top, height = 50, ticks = 20:10:120))``
Tip

Like the `xticks` and `yticks` `Axis` customizations, `colorbar`’s `ticks` accepts either a vector or range.

## 7 🎨 Using themes

Most of the visual customizations you have seen above can be themed directly in Makie. This way, you don’t have to pass the same keyword arguments over and over again.

You can either use `set_theme!()` to set a theme that persists until `set_theme!()` is called again, or you use `with_theme()` which executes one function with a specific theme which is then reset.

Here are a couple of examples using `with_theme()` which apply analogously to `set_theme!()`. The themes used are Makie presets, which you can find out about in the Makie docs.

``````with_theme(theme_black()) do
draw(plt_colorbar)
end``````
``````with_theme(theme_ggplot2()) do
draw(plt_colorbar)
end``````

You can also override attributes from preset themes:

``````with_theme(theme_minimal(); Axis = (; bottomspinecolor = :red, leftspinecolor = :blue)) do
draw(plt_colorbar)
end``````

## 8 🪧 Conclusion

In this tutorial, we showcase how to fine-tune and customize your `AoG.jl` visualization. Please notice that most of these customizations call `Makie.jl` under the hood. We highly recommend browsing `Makie.jl`’s documentation. It is a rich source of references on anything “customizable” in `AoG.jl`.