5.6 布局
一个完整的 画布/布局 是由 Figure
定义的,创建后将在其中填充各种内容。 下面将以一个包含 Axis
,Legend
和 Colorbar
的简单例子开始。 在这项任务中, 就像 Array
/Matrix
那样,可以使用 rows
和 columns
索引 Figure
。 Axis
位于 第 1 行,第 1 列, 即为 fig[1, 1]
。 Colorbar
位于 第 1 行,第 2 列, 即为 fig[1, 2]
。 另外, Legend
位于 第 2 行 和 第 1 - 2 列, 即为 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.
这看起来已经不错了,但能变得更好。可以使用以下关键字和方法来解决图的间距问题:
figure_padding=(left, right, bottom, top)
padding=(left, right, bottom, top)
改变 Legend
或 Colorbar
实际大小的方法为:
tellheight=true
orfalse
tellwidth=true
orfalse
将这些设置为
true
后则需考虑Legend
或Colorbar
的实际大小(高或宽)。 然后这些内容将会相应地调整大小。
可以使用以下方法指定行和列的间距:
colgap!(fig.layout, col, separation)
rowgap!(fig.layout, row, separation)
列间距 (
colgap!
),如果给定了col
,那么间距将只应用在指定的列。 行间距 (rowgap!
),如果给定了row
,那么间距将只应用在指定的行。
接下来将学习如何将内容放进 突出部分(protrusion),即为 标题 x
和 y
,或 ticks
以及 label
保留的空间。 实现方法是将位置索引改为 fig[i, j, protrusion]
, 其中 protrusion
可以是 Left()
, Right()
,Bottom()
和 Top()
,或者是四个角 TopLeft()
, TopRight()
, BottomRight()
,BottomLeft()
。 这些选项将在如下的例子中使用:
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.
这里在 TopLeft()
添加标签 (a)
可能是不必要的, 因为标签仅在有两个以上的图时有意义。 在接下来的例子中,我们将继续使用之前的工具和一些新工具,并创建一个更丰富、更复杂的图。
可以使用以下函数隐藏图的装饰部分和轴线:
hidedecorations!(ax; kwargs...)
hidexdecorations!(ax; kwargs...)
hideydecorations!(ax; kwargs...)
hidespines!(ax; kwargs...)
应记住总是可以调用 help
查看能够传递的参数,例如:
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:
另外,对于 hidedecorations!
有:
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:
对于 不想隐藏的 元素,仅需要将它们的值设置为 false
,即 hideydecorations!(ax; ticks=false, grid=false)
。
同步 Axis
的方式如下:
linkaxes!
,linkyaxes!
和linkxaxes!
这在需要共享轴时会变得很有用。 另一种获得共享轴的方法是设置
limits!
。
使用以下方式可一次性设定limits
,当然也能单独为每个方向的轴单独设定:
limits!(ax; l, r, b, t)
,其中l
为左侧,r
右侧,b
底部, 和t
顶部。还能使用
ylims!(low, high)
或xlims!(low, high)
,甚至可以通过ylims!(low=0)
或xlims!(high=1)
只设定一边。
例子如下:
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.
如上所示, Colorbar
的方向已经变为水平且它的标签也处在其下方。 这是因为设定了 vertical=false
和 flipaxis=false
。 另外,也可以将更多的 Axis
添加到 fig
里,甚至可以是 Colorbar
和 Legend
,然后再构建布局。
另一种常见布局是热力图组成的正方网格:
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.
上图中每一个标签都位于 突出部分 并且每一个 Axis
都有 AspectData()
率属性。 图中 Colorbar
位于第三列,并从第一行跨到第二行。
下例将使用称为 Mixed()
的对齐模式,这在处理 Axis
间的大量空白区域时很有用,而这些空白区域通常是由长标签导致的。 另外,本例还需要使用 Julia 标准库中的 Dates
。
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.
如上,参数 alignmode=Mixed(bottom=0)
将边界框移动到底部,使其与左侧面板保持对齐。
从上图也可以看到 colsize!
和 rowsize!
如何作用于不同的行和列。 可以向函数传递一个数字而不是 Auto()
,但那会固定所有的设置。 另外, 在定义 Axis
时也可以设定 height
或 width
,例如 Axis(fig, heigth=50)
将会固定轴的高度。
5.6.1 嵌套 Axis
(subplots)
精准定义一组 Axis
(subplots) 也是可行的, 可以使用一组 Axis
构造具有多行多列的图。 例如,下面展示了一组较复杂的 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
当通过多次调用它来构建更复杂的图时,可以得到:
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.
注意,这里可以调用不同的子图函数。 另外,每一个 Axis
都是 Figure
的独立部分。 因此,当在进行 rowgap!
或者 colsize!
这样的操作时,你需要考虑是对每一个子图单独作用还是对所有的图一起作用。
对于组合的 Axis
(subplots) 可以使用 GridLayout()
, 它能用来构造更复杂的 Figure
。
5.6.2 嵌套网格布局
可以使用 GridLayout()
组合子图,这种方法能够更自由地构建更复杂的图。 这里再次使用之前的 nested_sub_plot!
,它定义了三组子图和一个普通的 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[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.
现在,对每一组使用 rowgap!
或 colsize!
将是可行的,并且 rowsize!, colsize!
也能够应用于 GridLayout()
。
5.6.3 插图
目前,绘制 inset
是一项棘手的工作。 本节展示两种在初始时通过定义辅助函数实现绘制插图的方法。 第一种是定义 BBox
,它存在于整个 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
然后可以按照如下方式轻松地绘制插图:
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.
其中 Box
的尺寸受到 Figure
中 resolution
参数的约束。 注意,也可以在 Axis
外绘制插图。 另一种绘制插图的方法是,在位置fig[i, j]
处定义一个新的 Axis
,并且指定 width
, height
, halign
和 valign
。 如下面的函数例子所示:
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
在下面的例子中,如果总图的大小发生变化,那么将重新缩放灰色背景的 Axis
。 同时 插图 要受到 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.
以上包含了 Makie 中布局选项的大多数常见用例。 现在,让我们接下来使用 GLMakie.jl
绘制一些漂亮的3D示例图。
CC BY-NC-SA 4.0 Jose Storopoli, Rik Huijzer, Lazaro Alonso, 刘贵欣 (中文翻译), 田俊 (中文审校)