'tight_layout' in Makie for plots with multiple subplots

I have the following code:

using CairoMakie

# create data
x = range(-1, 1, 129)
y = range(-1, 1, 129)

X = ones(length(y)) .* x'
Y = y .* ones(1,length(x))
R = sqrt.(X.^2 + Y.^2)

Z = ((R.^2 .- 1.5) .^ 2)
Z[R.>1] .= 0

clim = (0, maximum(Z)*4)

# create figure
fig_w = 2*(3+3/8)   # in inch
fig_h = 2.5           # in inch
size_pt = 72 .* (fig_w, fig_h)

set_theme!(lightposition=Vec3f(0, 2, 1))

cmap1 = Reverse(:Spectral_4)

fig = Figure(resolution=size_pt)

axes1 = []
axes2 = []
hm2 = []

for (i,idx) in enumerate(t_idx)

    ax1 = Axis3(fig[1,i], elevation=pi/8, azimuth=-pi/6)
    push!(axes1, ax1)

    surface!(ax1, X, Y, i*Z, shininess=200f0, specular=Vec3f(0.8), colormap=cmap1,
        colorrange=clim, rasterize=10)
    
    limits!(ax1, -1, 1, -1, 1, clim...)

    hidedecorations!(ax1)
    hidespines!(ax1)

    ax2 = Axis(fig[2,i], aspect=AxisAspect(1))
    push!(axes2, ax2)

    hm = heatmap!(ax2, X, Y, i*Z, colormap=cmap1, colorrange=clim)
    push!(hm2, hm)

    hidedecorations!(ax2)
    hidespines!(ax2)
end

cb = Colorbar(fig[1:2,5], hm2[end])

save("test_fig.pdf", fig, pt_per_unit=1)

fig

And this is what I get:

A couple issues:

  1. The numbers on the colorbar are missing i the produced pdf figure although they do appear in the figure preview in VSCode. I don’t know what’s wrong.

  2. The bigger problem: how do I remove those empty space? I have played around using colsize!, rowsize!, colgap!, rowgap! but I don’t seem to figure out the proper way to do it. When I was using Matplotlob, fig.tight_layout() does a pretty good job, and if not I can just use ax.set_position() to set the axis position manually to eliminate the margin. I would like to know if similar methods exist in Makie, and if not what’s the Makie way to do it properly.

Thanks

It looks like hidedecorations!(...) on an Axis3 does not reclaim space like it does for a 2D Axis.

let
	fig = Figure(resolution=(700, 300))
	for i in 1:4
		ax1 = Axis3(fig[1, i])
		hidedecorations!(ax1)  # same place taken if this is commented out
		ax2 = Axis(fig[2, i])
		hidedecorations!(ax2)
	end
	fig
end


without hidedecorations:

Using the protrusions keyword seems to fix it.

  • protrusions: The protrusions on the sides of the axis, how much gap space is reserved for labels etc. Default: 30
let
	fig = Figure(resolution=(700, 300))
	for i in 1:4
		ax1 = Axis3(fig[1, i]; protrusions=0)
		hidedecorations!(ax1)
		ax2 = Axis(fig[2, i])
		hidedecorations!(ax2)
	end
	fig
end

I would have expected hidedecorations! to do this automatically.

In your case:

image

Code
# create data
x = range(-1, 1, 129)
y = range(-1, 1, 129)

X = ones(length(y)) .* x'
Y = y .* ones(1,length(x))
R = sqrt.(X.^2 + Y.^2)

Z = ((R.^2 .- 1.5) .^ 2)
Z[R.>1] .= 0

clim = (0, maximum(Z)*4)

# create figure
fig_w = 2*(3+3/8)   # in inch
fig_h = 2.5           # in inch
size_pt = 72 .* (fig_w, fig_h)

set_theme!(lightposition=Vec3f(0, 2, 1))

cmap1 = Reverse(:Spectral_4)

fig = Figure(resolution=size_pt)

hm2 = []

for i in 1:4

    ax1 = Axis3(fig[1,i]; elevation=pi/8, azimuth=-pi/6, protrusions=0)

    surface!(ax1, X, Y, i*Z, shininess=200f0, specular=Vec3f(0.8), colormap=cmap1,
        colorrange=clim, rasterize=10)
    
    limits!(ax1, -1, 1, -1, 1, clim...)

    hidedecorations!(ax1)
    hidespines!(ax1)

    ax2 = Axis(fig[2,i])

    hm = heatmap!(ax2, X, Y, i*Z, colormap=cmap1, colorrange=clim)
    push!(hm2, hm)

    hidedecorations!(ax2)
    hidespines!(ax2)

	colsize!(fig.layout, i, Aspect(1, 1.0))
end


cb = Colorbar(fig[1:2,5], hm2[end])

save("test_fig.pdf", fig, pt_per_unit=1)

fig

and the PDF looks fine and sharp.

1 Like

We could think about doing that.
The issue is that the protrusions value of Axis3 is just a guess that works ok-ish by default, but not if you hide decorations, have really large ones, etc. The reason is that for Axis, how much the axis decorations protrude does only depend on the ticks, for Axis3 it does also depend on the exact projection. But because the projection changes with the layout space given to Axis3, the protrusions cannot also depend on this, otherwise you’d have a cycle in the layout logic.

Another option would be adding a function like auto_protrusions!(ax3) that tries to compute how far out the labels are currently sticking, and using that value. If you hid all of them, this would zero out the protrusions.

otherwise you’d have a cycle in the layout logic

Thanks, I understand now, and fully support your approach,
which made the new layout system so robust.
Better having to enter some fixed values in a few known places
than a solver that do frustrating things at the worst moment, on complex figures.

I filed issue #2259 to track potential improvements about 3D protrusions.