Make a Legend with width equal to an axis

Hi, I am trying to recreate the following PyPlot image with Makie:

I know how to make everything in the figure except one thing: how to make the legend width to be exactly the same as the axis below it. Any help would be appreciated. I have access to the Axis object.

I thought

Legend(fig[0, :], [l], ["test"]; tellheight = true, tellwidth = false, width = Auto())

would do the trick, but it doesn’t.

Have you tried setting one of the Axes to tellwidth=true?

Also, what does the figure look like now?

The axes will automatically take the legend width if they’re in the same single column. You can use resize_to_layout! to remove the excess whitespace of the Figure.

f = Figure()

axs = map(1:2) do i
    ax = Axis(f[i+1, 1])
    lines!(ax, cumsum(randn(100)), label = "periodic")
    lines!(ax, cumsum(randn(100)), label = "quasiperiodic")
    lines!(ax, cumsum(randn(100)), label = "Koch snowflake")
    lines!(ax, cumsum(randn(100)), label = "Kaplan-Yorke map")
    lines!(ax, cumsum(randn(100)), label = "sphere")
    lines!(ax, cumsum(randn(100)), label = "SM (uniform)")
    return ax
end

Legend(f[1, 1], axs[1], nbanks = 3, tellheight = true)

resize_to_layout!(f)

f

1 Like

Or maybe something like this:

julia> fig = Figure()
julia> ax = Axis(fig[2,1], tellwidth=true)
julia> ax2 = Axis(fig[3,1])
julia> [lines!(ax,rand(10),label=String(rand('a':'z',15))) for _ in 1:5]
julia> lg = Legend(fig[1,1],ax,"analytically shown Δ", tellheight=false, tellwidth=false, nbanks=3)
julia> lg.width[] = ax.width[]

Thank you all for the advice. Oh, I wish I had a solution between that of @jules and @aramirezreyes

The problem with @jules solution is that the figures are for publication; they need to have a specific size, otherwise it will messup the font size significantly when inserted into latex, and I really don’t want to deal with hte headache of the fontsize of the figure changing depending on how many letters my legend has…

@aramirezreyes solution is far superior here for the reason that the figure has a fixed size. Unfortunately, all legend entries are grouped in the middle, which appears weird.

By the way, since I know in advance the width of my figure, I could give all axes and the legend a fixed widht, like 700, but this doesn’t give results different from the @aramirezreyes solution:

f = Figure(resolution = (750, 500))
axs = map(1:2) do i
    ax = Axis(f[i+1, 1]; width = 700)
    lines!(ax, cumsum(randn(100)), label = "periodic")
    lines!(ax, cumsum(randn(100)), label = "quasiperiodic")
    lines!(ax, cumsum(randn(100)), label = "Koch snowflake")
    lines!(ax, cumsum(randn(100)), label = "Kaplan-Yorke map")
    lines!(ax, cumsum(randn(100)), label = "sphere")
    lines!(ax, cumsum(randn(100)), label = "SM (uniform)")
    return ax
end
Legend(f[1, 1], axs[1], nbanks = 3, tellheight = true, width = 700)
f

image

having an evenly spaced out legend regardless of width seems like a nice feature to have, especially for publications. The situation I am trtying to achieve is common in papers I believe.

No matter how advanced the tooling is, making plots always feels like artisanal labor.

f = Figure(resolution = (750, 500))
axs = map(1:2) do i
    ax = Axis(f[i+1, 1]; width = 700)
    lines!(ax, cumsum(randn(100)), label = "periodic")
    lines!(ax, cumsum(randn(100)), label = "quasiperiodic")
    lines!(ax, cumsum(randn(100)), label = "Koch snowflake")
    lines!(ax, cumsum(randn(100)), label = "Kaplan-Yorke map")
    lines!(ax, cumsum(randn(100)), label = "sphere")
    lines!(ax, cumsum(randn(100)), label = "SM (uniform)")
    return ax
end
 Legend(f[1, 1], axs[1],"analytically known Δ", nbanks = 3, tellheight = true, width = 700, patchsize=(40f0,20),colgap = 100)
f

Yeah but this seems to depend on the number of characters in my labels. I have 7 such figures in my paper so it becomes tedious to try and optimize these values for each one :smiley:

But of course for now I’ll just live with having some decently large gap size and patch size and don’t worry too much that things aren’t perfectly aligned!

You can measure the amount needed for the adjustment (using the rather internal but unlikely to change layout observables). You could also split the difference between column gaps and patch sizes depending on what you need:

f = Figure(resolution = (750, 500))

axs = map(1:2) do i
    ax = Axis(f[i+1, 1])
    lines!(ax, cumsum(randn(100)), label = "periodic")
    lines!(ax, cumsum(randn(100)), label = "quasiperiodic")
    lines!(ax, cumsum(randn(100)), label = "Koch snowflake")
    lines!(ax, cumsum(randn(100)), label = "Kaplan-Yorke map")
    lines!(ax, cumsum(randn(100)), label = "sphere")
    lines!(ax, cumsum(randn(100)), label = "SM (uniform)")
    return ax
end

function space_out!(legend)
    w_available = legend.layoutobservables.suggestedbbox[].widths[1]
    w_used = legend.layoutobservables.computedbbox[].widths[1]
    difference = w_available - w_used
    legend.colgap[] += difference / (legend.nbanks[] - 1)
    return
end

l1 = Legend(f[-1, 1], axs[1], "analytically known Δ", nbanks = 2, tellheight = true, tellwidth = false)
l2 = Legend(f[0, 1], axs[1], "analytically known Δ", nbanks = 3, tellheight = true, tellwidth = false)
l3 = Legend(f[1, 1], axs[1], "analytically known Δ", nbanks = 4, tellheight = true, tellwidth = false)

# update axis limits etc, the legend adjustment must come last
Makie.update_state_before_display!(f)

space_out!(l1)
space_out!(l2)
space_out!(l3)

f

5 Likes

Wow, thanks a lot; that is the solution!!!

1 Like

This is a really cool solution. Should we have this as a kwarg to the Legend constructor (by default false)?

1 Like

that wouldn’t work because you can only do this as the last step, and it depends on how exactly the legend is positioned whether it will work as intended