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()
seed!(123)
x, y, z = randn(6), randn(6), randn(6)
fig = Figure(size=(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")
fig
end
JDS.first_layout()
This does look good already, but it could be better. We could fix spacing problems using the following keywords and methods:
figure_padding=(left, right, bottom, top)
padding=(left, right, bottom, top)
Taking into account the actual size for a Legend
or Colorbar
is done by
tellheight=true
orfalse
tellwidth=true
orfalse
Setting these to
true
will take into account the actual size (height or width) for aLegend
orColorbar
. 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!
), ifcol
is given then the gap will be applied to that specific column. Row gap (rowgap!
), ifrow
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()
seed!(123)
x, y, z = randn(6), randn(6), randn(6)
fig = Figure(figure_padding=(0, 3, 5, 2), size=(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)
fig
end
JDS.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!
andlinkxaxes!
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()
seed!(123)
x = range(0, 1, 10)
y = range(0, 1, 10)
z = rand(10, 10)
fig = Figure(size=(700, 400),
font="CMU Serif",
backgroundcolor=:snow2
)
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)", 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)
fig
end
JDS.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()
seed!(123)
letters = reshape(collect('a':'d'), (2, 2))
fig = Figure(size=(600, 400), fontsize=14, font="CMU Serif",
backgroundcolor=:snow2)
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)
fig
end
JDS.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()
seed!(123)
longlabels = ["$(today() - Day(1))", "$(today())", "$(today() + Day(1))"]
fig = Figure(size=(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)
fig
end
JDS.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.
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)
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
JDS.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
.
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])
nested_sub_plot!(gb)
axsc = nested_sub_plot!(gc)
nested_sub_plot!(gd)
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))
fig
end
JDS.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.
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; backgroundcolor=:snow2,
left=100, right=250, bottom=200, top=300)
inset_box = Axis(fig, bbox=BBox(left, right, bottom, top),
xticklabelsize=12, yticklabelsize=12, backgroundcolor=backgroundcolor)
translate!(inset_box.scene, 0, 0, 10) # bring content upfront
return inset_box
end
Then, the inset
is easily done, as in:
function figure_box_inset()
fig = Figure(size=(600, 400))
ax = Axis(fig[1, 1], backgroundcolor=:white)
inset_ax1 = add_box_inset(fig; backgroundcolor=:snow2,
left=100, right=250, bottom=200, top=300)
inset_ax2 = add_box_inset(fig; backgroundcolor=(: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)
fig
end
JDS.figure_box_inset()
where the Box
dimensions are bound by the Figure
’s size
. 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]; backgroundcolor=: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=backgroundcolor)
# bring content upfront
translate!(inset_box.scene, 0, 0, 10)
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(size=(600, 400))
ax = Axis(fig[1, 1], backgroundcolor=:white)
inset_ax1 = add_axis_inset(fig[1, 1]; backgroundcolor=: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]; backgroundcolor=(: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)
fig
end
JDS.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
.