6.8 Layouts

A complete canvas/layout is defined by Figure, which can be filled with content after creation. We will start with a simple arrangement of one Axis, one Legend and one Colorbar. For this task we can think of the canvas as an arrangement of rows and columns in indexing a Figure much like a regular Array/Matrix. The Axis content will be in row 1, column 1, e.g. fig[1, 1], the Colorbar in row 1, column 2, namely fig[1, 2]. And the Legend in row 2 and across column 1 and 2, namely fig[2, 1:2].

function first_layout()
    x, y, z = randn(6), randn(6), randn(6)
    fig = Figure(resolution=(600, 400), backgroundcolor=:snow2)
    ax = Axis(fig[1, 1], backgroundcolor=:white)
    pltobj = scatter!(ax, x, y; color=z, label="scatters")
    lines!(ax, x, 1.1y; label="line")
    Legend(fig[2, 1:2], ax, "labels", orientation=:horizontal)
    Colorbar(fig[1, 2], pltobj, label="colorbar")
Figure 25: First Layout.

This does look good already, but it could be better. We could fix spacing problems using the following keywords and methods:

Taking into account the actual size for a Legend or Colorbar is done by

  • tellheight=true or false
  • tellwidth=true or false

Setting these to true will take into account the actual size (height or width) for a Legend or Colorbar. Consequently, things will be resized accordingly.

The space between columns and rows is specified as

  • colgap!(fig.layout, col, separation)
  • rowgap!(fig.layout, row, separation)

Column gap (colgap!), if col is given then the gap will be applied to that specific column. Row gap (rowgap!), if row is given then the gap will be applied to that specific row.

Also, we will see how to put content into the protrusions, i.e. the space reserved for title: x and y; either ticks or label. We do this by plotting into fig[i, j, protrusion] where protrusion can be Left(), Right(), Bottom() and Top(), or for each corner TopLeft(), TopRight(), BottomRight(), BottomLeft(). See below how these options are being used:

function first_layout_fixed()
    x, y, z = randn(6), randn(6), randn(6)
    fig = Figure(figure_padding=(0, 3, 5, 2), resolution=(600, 400),
        backgroundcolor=:snow2, font="CMU Serif")
    ax = Axis(fig[1, 1], xlabel=L"x", ylabel=L"y",
        title="Layout example", backgroundcolor=:white)
    pltobj = scatter!(ax, x, y; color=z, label="scatters")
    lines!(ax, x, 1.1y; label="line")
    Legend(fig[2, 1:2], ax, "Labels", orientation=:horizontal,
        tellheight=true, titleposition=:left)
    Colorbar(fig[1, 2], pltobj, label="colorbar")
    # additional aesthetics
    Box(fig[1, 1, Right()], color=(:snow4, 0.35))
    Label(fig[1, 1, Right()], "protrusion", fontsize=18,
        rotation=pi / 2, padding=(3, 3, 3, 3))
    Label(fig[1, 1, TopLeft()], "(a)", fontsize=18, padding=(0, 3, 8, 0))
    colgap!(fig.layout, 5)
    rowgap!(fig.layout, 5)
Figure 26: First Layout Fixed.

Here, having the label (a) in the TopLeft() is probably not necessary, this will only make sense for more than one plot. Also, note the use of padding, which allows more fine control over his position.

For our next example let’s keep using the previous tools and some more to create a richer and complex figure.

Having the same limits across different plots can be done via your Axis with:

  • linkaxes!, linkyaxes! and linkxaxes!

This could be useful when shared axis are desired. Another way of getting shared axis will be by setting limits!.

Now, the example:

function complex_layout_double_axis()
    x = range(0, 1, 10)
    y = range(0, 1, 10)
    z = rand(10, 10)
    fig = Figure(resolution=(700, 400),
        font="CMU Serif",
    ax1 = Axis(fig, xlabel=L"x", ylabel=L"y")
    ax2 = Axis(fig, xlabel=L"x")
    heatmap!(ax1, x, y, z; colorrange=(0, 1))
    series!(ax2, abs.(z[1:4, :]);
        labels=["lab $i" for i = 1:4], color=:Set1_4)
    hm = scatter!(10x, y; color=z[1, :], label="dots", colorrange=(0, 1))
    hideydecorations!(ax2, ticks=false, grid=false)
    linkyaxes!(ax1, ax2)
    fig[1, 1] = ax1
    fig[1, 2] = ax2
    Label(fig[1, 1, TopLeft()], "(a)", fontsize=18, padding=(0, 6, 8, 0))
    Label(fig[1, 2, TopLeft()], "(b)", fontsize=18, padding=(0, 6, 8, 0))
    Colorbar(fig[2, 1:2], hm, label="colorbar",
        vertical=false, flipaxis=false
    Legend(fig[1, 3], ax2, "Legend")
    colgap!(fig.layout, 5)
    rowgap!(fig.layout, 5)
Figure 27: Complex layout double axis.

So, now our Colorbar is horizontal and the bar ticks are in the lower part. This is done by setting vertical=false and flipaxis=false. Additionally, note that we can call many Axis into fig, or even Colorbar’s and Legend’s, and then afterwards build the layout.

Another common layout is a grid of squares for heatmaps:

function squares_layout()
    letters = reshape(collect('a':'d'), (2, 2))
    fig = Figure(resolution=(600, 400), fontsize=14, font="CMU Serif",
    axs = [Axis(fig[i, j], aspect=DataAspect()) for i = 1:2, j = 1:2]
    hms = [heatmap!(axs[i, j], randn(10, 10), colorrange=(-2, 2))
           for i = 1:2, j = 1:2]
    Colorbar(fig[1:2, 3], hms[1], label="colorbar")
    [Label(fig[i, j, TopLeft()], "($(letters[i, j]))", fontsize=16,
        padding=(-2, 0, -20, 0)) for i = 1:2, j = 1:2]
    colgap!(fig.layout, 5)
    rowgap!(fig.layout, 5)
Figure 28: Squares layout.

where all labels are in the protrusions and each Axis has an AspectData() ratio. The Colorbar is located in the third column and expands from row 1 up to row 2.

The next case uses the so called Mixed() alignmode, which is especially useful when dealing with large empty spaces between Axis due to long ticks. Also, the Dates module from Julia’s standard library will be needed for this example.

using Dates
function mixed_mode_layout()
    longlabels = ["$(today() - Day(1))", "$(today())", "$(today() + Day(1))"]
    fig = Figure(resolution=(600, 400), fontsize=12,
        backgroundcolor=:snow2, font="CMU Serif")
    ax1 = Axis(fig[1, 1], xlabel="x", alignmode=Mixed(bottom=0))
    ax2 = Axis(fig[1, 2], xticklabelrotation=π/2, alignmode=Mixed(bottom=0),
        xticks=([1, 5, 10], longlabels))
    ax3 = Axis(fig[2, 1:2])
    ax4 = Axis(fig[3, 1:2])
    axs = [ax1, ax2, ax3, ax4]
    [lines!(ax, 1:10, rand(10)) for ax in axs]
    hidexdecorations!(ax3; ticks=false, grid=false)
    Box(fig[2:3, 1:2, Right()], color=(:snow4, 0.35))
    Label(fig[2:3, 1:2, Right()], "protrusion", rotation=π/2, fontsize=14,
        padding=(3, 3, 3, 3))
    Label(fig[1, 1:2, Top()], "Mixed alignmode", fontsize=16,
        padding=(0, 0, 15, 0))
    colsize!(fig.layout, 1, Auto(2))
    rowsize!(fig.layout, 2, Auto(0.5))
    rowsize!(fig.layout, 3, Auto(0.5))
    rowgap!(fig.layout, 1, 15)
    rowgap!(fig.layout, 2, 0)
    colgap!(fig.layout, 5)
Figure 29: Mixed mode layout.

Here, the argument alignmode=Mixed(bottom=0) is shifting the bounding box to the bottom, so that this will align with the panel on the left filling the space.

Also, see how colsize! and rowsize! are being used for different columns and rows. You could also put a number instead of Auto() but then everything will be fixed. And, additionally, one could also give a height or width when defining the Axis, as in Axis(fig, height=50) which will be fixed as well.

6.8.1 Nested Axis (subplots)

It is also possible to define a set of Axis (subplots) explicitly, and use it to build a main figure with several rows and columns. For instance, the following is a “complicated” arrangement of Axis:

function nested_sub_plot!(f)
    backgroundcolor = rand(resample_cmap(:Pastel1_6, 6, alpha=0.25))
    ax1 = Axis(f[1, 1]; backgroundcolor)
    ax2 = Axis(f[1, 2]; backgroundcolor)
    ax3 = Axis(f[2, 1:2]; backgroundcolor)
    ax4 = Axis(f[1:2, 3]; backgroundcolor)
    return (ax1, ax2, ax3, ax4)

which, when used to build a more complex figure by doing several calls, we obtain:

function main_figure()
    fig = Figure()
    Axis(fig[1, 1])
    nested_sub_plot!(fig[1, 2])
    nested_sub_plot!(fig[1, 3])
    nested_sub_plot!(fig[2, 1:3])
Figure 30: Main figure.

Note that different subplot functions can be called here. Also, each Axis here is an independent part of Figure. So that, if you need to do some rowgap!’s or colsize!’s operations, you will need to do it in each one of them independently or to all of them together.

For grouped Axis (subplots) we can use GridLayout() which, then, could be used to compose a more complicated Figure.

6.8.2 Nested GridLayout

By using GridLayout() we can group subplots, allowing more freedom to build complex figures. Here, using our previous nested_sub_plot! we define three sub-groups and one normal Axis:

function nested_Grid_Layouts()
    fig = Figure(backgroundcolor=RGBf(0.96, 0.96, 0.96))
    ga = fig[1, 1] = GridLayout()
    gb = fig[1, 2] = GridLayout()
    gc = fig[1, 3] = GridLayout()
    gd = fig[2, 1:3] = GridLayout()
    gA = Axis(ga[1, 1])
    axsc = nested_sub_plot!(gc)
    hidedecorations!.(axsc, grid=false, ticks=false)
    colgap!(gc, 5)
    rowgap!(gc, 5)
    rowsize!(fig.layout, 2, Auto(0.5))
    colsize!(fig.layout, 1, Auto(0.5))
Figure 31: Nested Grid Layouts.

Now, using rowgap! or colsize! over each group is possible and rowsize!, colsize! can also be applied to the set of GridLayout()s.

6.8.3 Inset plots

Currently, doing inset plots is a little bit tricky. Here, we show two possible ways of doing it by initially defining auxiliary functions. The first one is by doing a BBox, which lives in the whole Figure space:

function add_box_inset(fig; bgcolor=:snow2,
    left=100, right=250, bottom=200, top=300)
    inset_box = Axis(fig, bbox=BBox(left, right, bottom, top),
        xticklabelsize=12, yticklabelsize=12, backgroundcolor=bgcolor)
    translate!(inset_box.scene, 0, 0, 10)  # bring content upfront
    return inset_box

Then, the inset is easily done, as in:

function figure_box_inset()
    fig = Figure(resolution=(600, 400))
    ax = Axis(fig[1, 1], backgroundcolor=:white)
    inset_ax1 = add_box_inset(fig; bgcolor=:snow2,
        left=100, right=250, bottom=200, top=300)
    inset_ax2 = add_box_inset(fig; bgcolor=(:white, 0.85),
        left=500, right=580, bottom=100, top=200)
    lines!(ax, 1:10)
    lines!(inset_ax1, 1:10)
    scatter!(inset_ax2, 1:10, color=:black)
Figure 32: Figure box inset.

where the Box dimensions are bound by the Figure’s resolution. Note, that an inset can be also outside the Axis. The other approach, is by defining a new Axis into a position fig[i, j] specifying his width, height, halign and valign. We do that in the following function:

function add_axis_inset(pos=fig[1, 1]; bgcolor=:snow2,
    halign, valign, width=Relative(0.5),height=Relative(0.35),
    alignmode=Mixed(left=5, right=5))
    inset_box = Axis(pos; width, height, halign, valign, alignmode,
        xticklabelsize=12, yticklabelsize=12, backgroundcolor=bgcolor)
    # bring content upfront
    translate!(inset_box.scene, 0, 0, 10)
    return inset_box

See that in the following example the Axis with gray background will be rescaled if the total figure size changes. The insets are bound by the Axis positioning.

function figure_axis_inset()
    fig = Figure(resolution=(600, 400))
    ax = Axis(fig[1, 1], backgroundcolor=:white)
    inset_ax1 = add_axis_inset(fig[1, 1]; bgcolor=:snow2,
        halign=:left, valign=:center,
        width=Relative(0.3), height=Relative(0.35),
        alignmode=Mixed(left=5, right=5, bottom=15))
    inset_ax2 = add_axis_inset(fig[1, 1]; bgcolor=(:white, 0.85),
        halign=:right, valign=:center,
        width=Relative(0.25), height=Relative(0.3))
    lines!(ax, 1:10)
    lines!(inset_ax1, 1:10)
    scatter!(inset_ax2, 1:10, color=:black)
Figure 33: Figure axis inset.

And this should cover most used cases for layouting with Makie. Now, let’s do some nice 3D examples with GLMakie.jl.

Support this project
CC BY-NC-SA 4.0 Jose Storopoli, Rik Huijzer, Lazaro Alonso