5.6 Layouts

Um canvas/layout completo é definido por Figure, que pode ser preenchido com conteúdo após ser criado. Começaremos com um arranjo simples de um Axis, uma Legend e uma Colorbar. Para esta tarefa, podemos pensar no canvas como um arranjo de rows e columns na indexação de uma Figure bem como um Array/Matrix regular. O conteúdo do Axis estará na linha 1, coluna 1, por exemplo fig[1, 1], a Colorbar na linha 1, coluna 2, ou seja, fig[1, 2]. E a Legend na linha 2 e nas colunas 1 e 2, ou seja, fig[2, 1:2].

function first_layout()
    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.

Isso já parece bom, mas poderia ser melhor. Podemos corrigir problemas de espaçamento usando as seguintes palavras-chave e métodos:

Levar em consideração o tamanho real de uma Legend ou Colorbar é feito por:

  • tellheight=true ou false
  • tellwidth=true ou false

Definir como true levará em consideração o tamanho real (altura ou largura) para uma Legend ou Colorbar. Consequentemente, as coisas serão redimensionadas de acordo.

O espaço entre colunas e linhas é especificado como:

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

Column gap (colgap!), se col for fornecido, a lacuna será aplicada a essa coluna específica. Row gap (rowgap!) , se a linha for fornecido, a lacuna será aplicada a essa linha específica.

Além disso, veremos como colocar conteúdo nas protrusões, i.e. o espaço reservado para título: x e y; ou ticks ou label. Fazemos isso plotando em fig[i, j, protrusion] onde protrusion pode ser Left(), Right(), Bottom() e Top(), ou para cada canto TopLeft(), TopRight(), BottomRight(), BottomLeft(). Veja abaixo como essas opções estão sendo utilizadas:

function first_layout_fixed()
    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.

Aqui, ter o rótulo (a) no TopLeft() provavelmente não é necessário, isso só fará sentido para mais de dois plots. Para o nosso próximo exemplo vamos continuar usando as ferramentas anteriores e mais algumas para criar uma figura mais rica e complexa.

Você pode ocultar decorações e espinhas de eixos com:

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

Lembre-se, sempre podemos pedir ajuda para ver que tipo de argumentos podemos usar, por exemplo,

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!} are:

  

Alternativamente, para decorações:

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!} are:

  

Para elementos que você não deseja ocultar, apenas passe-os com false, ou seja, hideydecorations!(ax; ticks=false, grid=false).

A sincronização do seu Axis é feita via:

  • linkaxes!, linkyaxes! e linkxaxes!

Isso pode ser útil quando eixos compartilhados são desejados. Outra maneira de obter eixos compartilhados será definindo limites!.

Definir limites de uma vez ou independentemente para cada eixo é feito chamando

  • limits!(ax; l, r, b, t), onde l é esquerda, r direita, b inferior e t superior.

Você também pode fazer ylims!(low, high) ou xlims!(low, high), e até mesmo abrir fazendo ylims!(low=0) ou xlims!(high=1).

Agora, o exemplo:

function complex_layout_double_axis()
    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 = 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.

Então, agora nosso Colorbar precisa ser horizontal e as marcações da barra precisam estar na parte inferior. Isso é feito configurando vertical=false e flipaxis=false. Além disso, observe que podemos chamar muitos Axis em fig, ou mesmo Colorbar e Legend, e depois construir o layout.

Outro layout comum é uma grade de quadrados para mapas de calor:

function squares_layout()
    seed!(123)
    letters = reshape(collect('a':'d'), (2, 2))
    fig = Figure(resolution=(600, 400), fontsize=14, font="CMU Serif",
        backgroundcolor=:grey90)
    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]))", textsize=16,
        padding=(-2, 0, -20, 0)) for i = 1:2, j = 1:2]
    colgap!(fig.layout, 5)
    rowgap!(fig.layout, 5)
    fig
end
squares_layout()
Figure 30: Squares layout.

onde todos os rótulos estão em protrusões e cada Axis tem uma razão AspectData(). A Colorbar está localizada na terceira coluna e se expande da linha 1 até a linha 2.

O próximo caso usa o chamado modo de alinhamento Mixed(), o que é especialmente útil ao lidar com grandes espaços vazios entre Axis devido a tiques longos. Ainda, o módulo Dates da biblioteca padrão de Julia será necessário para esse exemplo.

using Dates
function mixed_mode_layout()
    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])
    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=(: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.

Aqui, o argumento alignmode=Mixed(bottom=0) desloca a caixa delimitadora para a parte inferior, de forma a alinhar com o painel à esquerda preenchendo o espaço.

Também, veja como colsize! e rowsize! estão sendo usados para diferentes colunas e linhas. Você também pode colocar um número ao invés de Auto() mas então tudo vai ser corrigido. E, além disso, pode-se também dar um height ou width ao definir o Axis, como em Axis(fig, heigth=50) que será corrigido também.

5.6.1 Axis aninhado (subplots)

Também é possível definir um conjunto de Axis (subplots) explicitamente e use-o para construir uma figura principal com várias linhas e colunas. Por exemplo, o seguinte é um arranjo “complicado” de Axis:

function nested_sub_plot!(fig)
    color = rand(RGBf)
    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

que, quando usado para construir uma figura mais complexa fazendo várias chamadas, obtemos:

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.

Observe que diferentes funções de subplot podem ser chamadas aqui. Também, cada Axis aqui é uma parte independente de Figure. Então, se você precisar fazer alguma operação rowgap! ou colsize!, você precisará fazê-lo em cada um deles de forma independente ou em todos eles juntos.

Para Axis (subplots) agrupados podemos usar GridLayout() que, então, poderia ser usado para compor um Figure.

5.6.2 GridLayout aninhado

Ao usar o GridLayout() podemos agrupar subplots, permitindo mais liberdade na construção de figuras complexas. Aqui, usando nosso nested_sub_plot! anterior, definimos três subgrupos e um Axis normal:

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])
    nested_sub_plot!(gb)
    axsc = nested_sub_plot!(gc)
    nested_sub_plot!(gd)
    [hidedecorations!(axsc[i], grid=false, ticks=false) for i = 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.

Agora, usando rowgap! ou colsize! sobre cada grupo é possível rowsize!, colsize! também pode ser aplicado ao conjunto de GridLayout().

5.6.3 Plots inset

Atualmente, fazer gráficos inset é um pouco complicado. Aqui, mostramos duas maneiras possíveis de fazer isso definindo inicialmente as funções auxiliares. A primeira é fazendo um BBox, que fica em todo o espaço Figure:

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

Então, o inset é feito facilmente, como em:

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, 0.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.

onde as dimensões Box estão vinculadas ao resolution Figure. Observe que um inset também pode estar fora do Axis. A outra abordagem é definir um novo Axis em uma posição fig[i, j] especificando seu width, height, halign and valign. Fazemos isso na seguinte função:

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

Veja que no exemplo a seguir o Axis com fundo cinza será redimensionado se o tamanho total da figura for alterado. Os insets são limitados pelo posicionamento do Axis.

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.

E isso deve cobrir os casos mais usados para layout com Makie. Agora, vamos fazer alguns bons exemplos 3D com GLMakie.jl.



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