6.6 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()
    Random.seed!(123)
    x, y, z = randn(6), randn(6), randn(6)

    fig = Figure(resolution = (600,400), backgroundcolor = :grey90)
    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")
    fig
end
first_layout()
Figure 27: First layout.

This does not look good. Next, we fix the 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()
    Random.seed!(123)
    x, y, z = randn(6), randn(6), randn(6)

    fig = Figure(figure_padding = (0,3,5,2), resolution = (600,400),
        backgroundcolor = :grey90, 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 = (:slateblue1,0.35))
    Label(fig[1, 1, Right()], "protrusion", textsize = 18,
        rotation = pi/2, padding =(3,3,3,3))
    Label(fig[1, 1, TopLeft()], "(a)", textsize = 18, padding = (0,3,8,0))
    colgap!(fig.layout, 5)
    rowgap!(fig.layout, 5)
    fig
end
first_layout_fixed()
Figure 28: First layout fixed.

Here, having the label (a) in the TopLeft() is probably not necessary, this will only make sense for more than two plots. For our next example let’s keep using the previous tools and some more to create a richer and complex figure.

You can hide decorations and axis’ spines with:

  • hidedecorations!(ax; kwargs...)
  • hidexdecorations!(ax; kwargs...)
  • hideydecorations!(ax; kwargs...)
  • hidespines!(ax; kwargs...)

Remember, we can always ask for help to see what kind of arguments we can use, e.g.,

help(hidespines!)
  hidespines!(la::Axis, spines::Symbol... = (:l, :r, :b, :t)...)

  Hide all specified axis spines. Hides all spines by default, otherwise
  choose with the symbols :l, :r, :b and :t.

  hidespines! has the following function signatures:

    (Vector, Vector)
    (Vector, Vector, Vector)
    (Matrix)

  Available attributes for Combined{Makie.MakieLayout.hidespines!, T} where T
  are:

  

Alternatively, for decorations

help(hidedecorations!)
  hidedecorations!(la::Axis)

  Hide decorations of both x and y-axis: label, ticklabels, ticks and grid.

  hidedecorations! has the following function signatures:

    (Vector, Vector)
    (Vector, Vector, Vector)
    (Matrix)

  Available attributes for Combined{Makie.MakieLayout.hidedecorations!, T}
  where T are:

  

For elements that you don’t want to hide, just pass them with false, i.e. hideydecorations!(ax; ticks=false, grid=false).

Synchronizing your Axis is done via:

  • linkaxes! linkyaxes! linkxaxes!

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

Setting limits at once or independently for each axis is done by calling

  • limits!(ax; l,r,b,t) You can also do ylims!(low, high) or xlims!(low, high), and even open ones by doing ylims!(low=0) or xlims!(high=1).

Now, the example:

function complex_layout_double_axis()
    Random.seed!(123)
    x = LinRange(0,1,10)
    y = LinRange(0,1,10)
    z = rand(10,10)

    fig = Figure(resolution = (600,400), font="CMU Serif",
        backgroundcolor = :grey90)
    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 in 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)
    #layout
    fig[1,1] = ax1
    fig[1,2] = ax2
    Label(fig[1, 1,TopLeft()], "(a)",textsize = 18,padding = (0,6,8,0))
    Label(fig[1, 2,TopLeft()], "(b)",textsize = 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)
    fig

end
complex_layout_double_axis()
Figure 29: Complex layout double axis.

So, now our Colorbar needs to be horizontal and the bar ticks need to be 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()
    Random.seed!(123)
    letters = reshape(collect('a':'d'), (2,2))
    fig = Figure(resolution =(500,400),fontsize = 14, font="CMU Serif",
        backgroundcolor = :grey90)
    axs = [Axis(fig[i, j], aspect = DataAspect()) for i in 1:2, j in 1:2]
    hms = [heatmap!(axs[i,j], randn(10,10), colorrange = (-2,2))
        for i in 1:2, j in 1:2]

    Colorbar(fig[1:2, 3], hms[1], label = "colorbar")
    [Label(fig[i, j, TopLeft()], "($(letters[i,j]))", textsize = 16,
        padding = (-24,0,-16,0)) for i in 1:2, j in 1:2]

    colgap!(fig.layout, 5)
    rowgap!(fig.layout, 5)
    fig
end
squares_layout()
Figure 30: 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.

function mixed_mode_layout()
    Random.seed!(123)
    longlabels = ["$(today() - Day(1))","$(today())","$(today() + Day(1))"]

    fig = Figure(resolution = (600,400), fontsize = 12,
        backgroundcolor = :grey90, font="CMU Serif")
    ax1 = Axis(fig[1,1])
    ax2 = Axis(fig[1,2],xticklabelrotation =pi/2,alignmode =Mixed(bottom=0),
        xticks = ([1,5,10], longlabels))
    ax3 =  Axis(fig[2,1:2])
    ax4 =  Axis(fig[3,1:2])

    lines!(ax1, 1:10, rand(10))
    lines!(ax2, 1:10, rand(10))
    lines!(ax3, 1:10, rand(10))
    lines!(ax4, 1:10, rand(10))
    hidexdecorations!(ax3; ticks = false, grid = false)

    Box(fig[2:3,1:2, Right()], color = (:slateblue1, 0.35))
    Label(fig[2:3,1:2, Right()],"protrusion",rotation = pi/2,textsize = 14,
        padding = (3,3,3,3))
    Label(fig[1,1:2, Top()], "Mixed alignmode", textsize = 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)
    fig
end
mixed_mode_layout()
Figure 31: 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, heigth = 50) which will be fixed as well.

6.6.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 its a “complicated” arrangement of Axis:

function nested_sub_plot!(fig)
    color = rand(RGBf0)
    ax1 = Axis(fig[1,1], backgroundcolor = (color,0.25))
    ax2 = Axis(fig[1,2], backgroundcolor = (color,0.25))
    ax3 = Axis(fig[2,1:2], backgroundcolor = (color,0.25))
    ax4 = Axis(fig[1:2,3], backgroundcolor = (color,0.25))
    return (ax1,ax2,ax3,ax4)
end

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])
    fig
end
main_figure()
Figure 32: 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 composed a more complicated Figure.

6.6.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 = RGBf0(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])
    nested_sub_plot!(gb)
    axsc = nested_sub_plot!(gc)
    nested_sub_plot!(gd)

    [hidedecorations!(axsc[i],grid=false,ticks=false) for i in 1:length(axsc)]

    colgap!(gc, 5)
    rowgap!(gc, 5)
    rowsize!(fig.layout, 2, Auto(0.5))
    colsize!(fig.layout, 1, Auto(0.5))
    fig
end
nested_Grid_Layouts()
Figure 33: 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.6.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;left=100,right=250,bottom = 200,top= 300,
        bgcolor=:grey90)
    inset_box = Axis(fig, bbox = BBox(left, right, bottom, top),
        xticklabelsize = 12, yticklabelsize=12, backgroundcolor = bgcolor)

    # bring content upfront
    # bring content upfront
    translate!(inset_box.scene, 0, 0, 10)
    elements = keys(inset_box.elements)
    filtered = filter(ele -> ele != :xaxis && ele !=:yaxis , elements)
    foreach(ele -> translate!(inset_box.elements[ele], 0, 0, 9), filtered)
    return inset_box
end

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; left=100,right=250,bottom = 200,top= 300,
        bgcolor = :grey90)
    inset_ax2 = add_box_inset(fig; left=500,right=600,bottom = 100,top= 200,
        bgcolor = (:white,.65))

    lines!(ax, 1:10)
    lines!(inset_ax1, 1:10)
    scatter!(inset_ax2, 1:10, color = :black)
    fig
end
figure_box_inset()
Figure 34: 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], halign=0.1, valign=0.5,
        width = Relative(0.5), height= Relative(0.35), bgcolor=:lightgray)

    inset_box = Axis(pos, width = width, height = height,
        halign=halign, valign=valign, xticklabelsize = 12,yticklabelsize=12,
        backgroundcolor = bgcolor)

    # bring content upfront
    translate!(inset_box.scene, 0, 0, 10)
    elements = keys(inset_box.elements)
    filtered = filter(ele -> ele != :xaxis && ele !=:yaxis , elements)
    foreach(ele -> translate!(inset_box.elements[ele], 0, 0, 9), filtered)
    return inset_box
end

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(; pos = fig[1,1], halign=0.1, valign=0.65,
        width = Relative(0.3), height= Relative(0.3), bgcolor= :grey90)
    inset_ax2 = add_axis_inset(; pos = fig[1,1], halign=1, valign=0.25,
        width = Relative(0.25), height= Relative(0.3), bgcolor= (:white,0.65))

    lines!(ax, 1:10)
    lines!(inset_ax1, 1:10)
    scatter!(inset_ax2, 1:10, color = :black)
    fig
end
figure_axis_inset()
Figure 35: 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.



CC BY-NC-SA 4.0 Jose Storopoli, Rik Huijzer and Lazaro Alonso