Engee 文档

创建复杂的布局

该页面正在翻译中。

在本教程中,您将学习如何使用Makie的布局工具创建复杂的图形。

假设我们要创建下图:

final result

以下是完整的代码以供参考:

using CairoMakie
using Makie.FileIO
CairoMakie.activate!() # hide

f = Figure(backgroundcolor = RGBf(0.98, 0.98, 0.98),
    size = (1000, 700))
ga = f[1, 1] = GridLayout()
gb = f[2, 1] = GridLayout()
gcd = f[1:2, 2] = GridLayout()
gc = gcd[1, 1] = GridLayout()
gd = gcd[2, 1] = GridLayout()
axtop = Axis(ga[1, 1])
axmain = Axis(ga[2, 1], xlabel = "before", ylabel = "after")
axright = Axis(ga[2, 2])

linkyaxes!(axmain, axright)
linkxaxes!(axmain, axtop)

labels = ["treatment", "placebo", "control"]
data = randn(3, 100, 2) .+ [1, 3, 5]

for (label, col) in zip(labels, eachslice(data, dims = 1))
    scatter!(axmain, col, label = label)
    density!(axtop, col[:, 1])
    density!(axright, col[:, 2], direction = :y)
end

ylims!(axtop, low = 0)
xlims!(axright, low = 0)

axmain.xticks = 0:3:9
axtop.xticks = 0:3:9

leg = Legend(ga[1, 2], axmain)

hidedecorations!(axtop, grid = false)
hidedecorations!(axright, grid = false)
leg.tellheight = true

colgap!(ga, 10)
rowgap!(ga, 10)

Label(ga[1, 1:2, Top()], "Stimulus ratings", valign = :bottom,
    font = :bold,
    padding = (0, 0, 5, 0))

xs = LinRange(0.5, 6, 50)
ys = LinRange(0.5, 6, 50)
data1 = [sin(x^1.5) * cos(y^0.5) for x in xs, y in ys] .+ 0.1 .* randn.()
data2 = [sin(x^0.8) * cos(y^1.5) for x in xs, y in ys] .+ 0.1 .* randn.()

ax1, hm = contourf(gb[1, 1], xs, ys, data1,
    levels = 6)
ax1.title = "Histological analysis"
contour!(ax1, xs, ys, data1, levels = 5, color = :black)
hidexdecorations!(ax1)

ax2, hm2 = contourf(gb[2, 1], xs, ys, data2,
    levels = 6)
contour!(ax2, xs, ys, data2, levels = 5, color = :black)

cb = Colorbar(gb[1:2, 2], hm, label = "cell group")
low, high = extrema(data1)
edges = range(low, high, length = 7)
centers = (edges[1:6] .+ edges[2:7]) .* 0.5
cb.ticks = (centers, string.(1:6))

cb.alignmode = Mixed(right = 0)

colgap!(gb, 10)
rowgap!(gb, 10)

brain = load(assetpath("brain.stl"))

ax3d = Axis3(gc[1, 1], title = "Brain activation")
m = mesh!(
    ax3d,
    brain,
    color = [tri[1][2] for tri in brain for i in 1:3],
    colormap = Reverse(:magma),
)
Colorbar(gc[1, 2], m, label = "BOLD level")

axs = [Axis(gd[row, col]) for row in 1:3, col in 1:2]
hidedecorations!.(axs, grid = false, label = false)

for row in 1:3, col in 1:2
    xrange = col == 1 ? (0:0.1:6pi) : (0:0.1:10pi)

    eeg = [sum(sin(pi * rand() + k * x) / k for k in 1:10)
        for x in xrange] .+ 0.1 .* randn.()

    lines!(axs[row, col], eeg, color = (:black, 0.5))
end

axs[3, 1].xlabel = "Day 1"
axs[3, 2].xlabel = "Day 2"

Label(gd[1, :, Top()], "EEG traces", valign = :bottom,
    font = :bold,
    padding = (0, 0, 5, 0))

rowgap!(gd, 10)
colgap!(gd, 10)

for(i,label)in enumerate(["睡眠","清醒","测试"])
    Box(gd[i,3],color=:gray90)
    标签(gd[i,3],标签,旋转=pi/2,tellheight=false)
结束

科尔加普!(gd,2,0)

n_day_1=长度(0:0.1:6pi)
n_day_2=长度(0:0.1:10pi)

科尔斯特!(gd,1,自动(n_day_1))
科尔斯特!(gd,2,自动(n_day_2))

对于(标签,布局)在zip(["A","B","C","D"],[ga,gb,gc,gd])
    标签(布局[1,1,TopLeft()],标签,
        字体大小=26,
        字体=:粗体,
        填充物= (0, 5, 5, 0),
        halign=:右)
结束

科尔斯特!(f.布局,1,自动(0.5))

rowsize!(gcd,1,自动(1.5))

f

我们如何处理这项任务?

在以下部分中,我们将逐步介绍该过程。 我们并不总是使用尽可能短的语法,因为主要目标是更好地理解逻辑和可用选项。

基本设计图

在构建数字时,你总是用矩形框来思考。 我们希望找到包含有意义的内容组的最大框,然后我们使用以下方法实现这些框 网格布局 或者通过将内容对象放置在那里。

如果我们看一下我们的目标图,我们可以想象在每个标记区域a,B,C和D周围有一个盒子。但是A和C不在一行,B和D也不在一行。这意味着我们不使用2x2GridLayout,但必须更具创造性。

我们可以说A和B在一列中,C和D在一列中。 我们可以通过一个大的嵌套来为两个组设置不同的行高 网格布局 在第二列中,我们放置C和D。这样,列2的行与列1解耦。

好的,我们先用一个灰色的backgroundcolor和一个预定义的字体来创建图形: </无翻译>

using CairoMakie
using FileIO

f = Figure(backgroundcolor = RGBf(0.98, 0.98, 0.98),
    size = (1000, 700))
eef34ae

设置网格布局

现在,让我们制作四个嵌套的GridLayouts来保存a,B,C和D的对象。还有将C和D放在一起的布局,因此行与A和B分开。

请注意,首先创建单独的并不是绝对必要的 网格布局s,然后使用它们在图中放置对象。 您还可以使用多个索引隐式创建嵌套网格,例如 轴(f[1, 2:3][4:5, 6]). 这在进一步解释 GridPositions和GridSubpositions。 但是,如果您想在之后操作嵌套网格,例如更改列大小或行间隙,那么如果您已经将它们存储在变量中,则会更容易。

ga = f[1, 1] = GridLayout()
gb = f[2, 1] = GridLayout()
gcd = f[1:2, 2] = GridLayout()
gc = gcd[1, 1] = GridLayout()
gd = gcd[2, 1] = GridLayout()

小组A

现在我们可以开始将对象放入图中。 我们从A开始。

有三把斧头和一个传说. 我们可以首先放置轴,适当地链接它们,并将第一个数据绘制到它们中。 </无翻译>

axtop = Axis(ga[1, 1])
axmain = Axis(ga[2, 1], xlabel = "before", ylabel = "after")
axright = Axis(ga[2, 2])

linkyaxes!(axmain, axright)
linkxaxes!(axmain, axtop)

labels = ["treatment", "placebo", "control"]
data = randn(3, 100, 2) .+ [1, 3, 5]

for (label, col) in zip(labels, eachslice(data, dims = 1))
    scatter!(axmain, col, label = label)
    density!(axtop, col[:, 1])
    density!(axright, col[:, 2], direction = :y)
end

f
1026493

密度图和它们的轴之间有一个小间隙,我们可以通过固定一侧的限制来消除。 </无翻译>

ylims!(axtop, low = 0)
xlims!(axright, low = 0)

f
6da9928

我们也可以用整数选择不同的x刻度。 </无翻译>

axmain.xticks = 0:3:9
axtop.xticks = 0:3:9

f
8c17e6a

传说

我们已经设定了 标签 散点调用中的属性,以便更容易构造图例。 我们可以通过 阿克斯曼 作为第二个论点 传说. </无翻译>

leg = Legend(ga[1, 2], axmain)

f
def45c2

传奇调整

有几件事我们想改变。 侧轴有不必要的装饰,我们要隐藏。

此外,顶轴不具有与图例相同的高度。 这是因为图例通常用于 轴心,轴心 并因此预设有 tellheight=错误. 我们将此属性设置为 真的 因此,图例所在的行可以收缩到其已知大小。 </无翻译>

hidedecorations!(axtop, grid = false)
hidedecorations!(axright, grid = false)
leg.tellheight = true

f
3713ad1

轴之间的距离仍然有点太远,所以我们减少了列和行间隙。 </无翻译>

colgap!(ga, 10)
rowgap!(ga, 10)

f
9b0f189

我们可以通过在顶部两个元素上放置一个标签来制作标题。 </无翻译>

Label(ga[1, 1:2, Top()], "Stimulus ratings", valign = :bottom,
    font = :bold,
    padding = (0, 0, 5, 0))

f
212899f

小组B

让我们移动到B。我们有两个轴堆叠在彼此的顶部,和一个颜色栏在他们旁边。 这一次,我们只需在右边绘制轴即可创建轴 网格布局 插槽. 这比创建一个 轴心,轴心 首先。 </无翻译>

xs = LinRange(0.5, 6, 50)
ys = LinRange(0.5, 6, 50)
data1 = [sin(x^1.5) * cos(y^0.5) for x in xs, y in ys] .+ 0.1 .* randn.()
data2 = [sin(x^0.8) * cos(y^1.5) for x in xs, y in ys] .+ 0.1 .* randn.()

ax1, hm = contourf(gb[1, 1], xs, ys, data1,
    levels = 6)
ax1.title = "Histological analysis"
contour!(ax1, xs, ys, data1, levels = 5, color = :black)
hidexdecorations!(ax1)

ax2, hm2 = contourf(gb[2, 1], xs, ys, data2,
    levels = 6)
contour!(ax2, xs, ys, data2, levels = 5, color = :black)

f
c2a5039

颜色栏

现在我们需要一个colorbar。 因为我们没有为两个等高线图设置特定的边,所以我们可以使用其中一个等高线图制作一个colorbar,然后在一个到六个之间标记箱。 </无翻译>

cb = Colorbar(gb[1:2, 2], hm, label = "cell group")
low, high = extrema(data1)
edges = range(low, high, length = 7)
centers = (edges[1:6] .+ edges[2:7]) .* 0.5
cb.ticks = (centers, string.(1:6))

f
590d3ee

混合排列模式

Colorbar的右边缘当前与上部密度图的右边缘对齐。 这可能会在密度图和右边的内容之间造成一点差距。

为了改进这一点,我们可以使用 混合型 对齐模式。 关键字 右=0 意味着colorbar的右侧应该向内拉动其突出部分内容,并带有额外的填充 0. </无翻译>

cb.alignmode = Mixed(right = 0)

f
5fe239e

就像在A中一样,轴之间的距离有点太远了。 </无翻译>

colgap!(gb, 10)
rowgap!(gb, 10)

f
9d09c8c

小组C

现在,我们进入面板C。这只是一个 轴心3 侧面有一个色条。 </无翻译>

brain = load(assetpath("brain.stl"))

ax3d = Axis3(gc[1, 1], title = "Brain activation")
m = mesh!(
    ax3d,
    brain,
    color = [tri[1][2] for tri in brain for i in 1:3],
    colormap = Reverse(:magma),
)
Colorbar(gc[1, 2], m, label = "BOLD level")

f
acc46bb

请注意,z标签与左侧的绘图重叠了一点。 轴心3 不能有自动突起,因为标签位置随着投影和轴的像元大小而变化,这与2D不同 轴心,轴心.

您可以设置属性 ax3。突起物 到四个值(左,右,底部,顶部)的元组,但在这种情况下,我们只是继续绘制,直到我们拥有我们想要的所有对象,然后再查看是否需要这样的小调整。

小组D

我们移动到面板D,它有一个3x2轴的网格。 </无翻译>

axs = [Axis(gd[row, col]) for row in 1:3, col in 1:2]
hidedecorations!.(axs, grid = false, label = false)

for row in 1:3, col in 1:2
    xrange = col == 1 ? (0:0.1:6pi) : (0:0.1:10pi)

    eeg = [sum(sin(pi * rand() + k * x) / k for k in 1:10)
        for x in xrange] .+ 0.1 .* randn.()

    lines!(axs[row, col], eeg, color = (:black, 0.5))
end

axs[3, 1].xlabel = "Day 1"
axs[3, 2].xlabel = "Day 2"

f
f2ddd8e

我们可以通过放置一个小标题为六个轴 标签 行1的顶部突出部中且横跨两列。 </无翻译>

Label(gd[1, :, Top()], "EEG traces", valign = :bottom,
    font = :bold,
    padding = (0, 0, 5, 0))

f
14fb186

同样,我们通过减少间隙大小来使子图更接近。 </无翻译>

rowgap!(gd, 10)
colgap!(gd, 10)

f
5dec720

脑电图标签

现在,我们在侧面添加三个带有标签的盒子。 在这种情况下,我们只需将它们放在右侧的另一列中。 </无翻译>

for (i, label) in enumerate(["sleep", "awake", "test"])
    Box(gd[i, 3], color = :gray90)
    Label(gd[i, 3], label, rotation = pi/2, tellheight = false)
end

f
21ea95a

盒子在正确的位置,但我们仍然需要删除列间隙。 </无翻译>

colgap!(gd, 2, 0)

f
7c6428a

缩放轴相对

我们创建的假脑电图数据在第2天比第1天有更多的数据点。 我们希望缩放轴,使它们都具有相同的缩放级别。 我们可以通过将列宽设置为 自动(x) 其中x是与轴的数据点数量成比例的数字。 这样,两者都将具有相同的相对缩放。 </无翻译>

n_day_1 = length(0:0.1:6pi)
n_day_2 = length(0:0.1:10pi)

colsize!(gd, 1, Auto(n_day_1))
colsize!(gd, 2, Auto(n_day_2))

f
8e7a54f

子图标签

现在,我们可以添加子图标签。 我们已经有四个了 网格布局 包含每个面板内容的对象,因此最简单的方法是创建 标签s在这些布局的左上突出。 这将使所有其他对齐保持不变,因为我们没有创建任何新的列或行。 标签属于布局之间的间隙。 </无翻译>

for (label, layout) in zip(["A", "B", "C", "D"], [ga, gb, gc, gd])
    Label(layout[1, 1, TopLeft()], label,
        fontsize = 26,
        font = :bold,
        padding = (0, 5, 5, 0),
        halign = :right)
end

f
68ff944

最后的调整

这看起来已经相当不错了,但是布局的第一列有点太宽了。 我们可以通过将列宽设置为 汽车 以小于1的数字为例。 当在所有列之间分配宽度时,这使列具有较小的权重 汽车 尺寸。

您也可以使用 亲戚固定 但是如果你以后添加更多的东西,它们就不那么灵活了,所以我更喜欢使用 汽车. </无翻译>

colsize!(f.layout, 1, Auto(0.5))

f
982a1a3

脑电图痕迹目前与脑轴一样高,让我们将面板C布局的行的大小增加一点,这样它就有更多的空间。

这就是最终的结果: </无翻译>

rowsize!(gcd, 1, Auto(1.5))

f
c2b8e9e