How can I achieve this Makie Layout that respects a given aspect ratio?

I want to make the following layout behave sensibly under changes of the figure size (interactive settings mainly).

image

The constraints are:

  1. The green column has a fixed width.
  2. The red box has a given aspect ratio (1 in this case).
  3. The blue box should have full width.
  4. The height of the blue box is less important, but it should not be below, say, 10% of the height of the right (red + blue) column.

So this is perfectly okay:

image

In contrast, this is something I want to prevent (either by introducing white space between blue and red, or by increasing the height of blue):

image

The current code:

using GLMakie

fig = Figure()
layout_right = fig[1,2] = GridLayout(2,1)
layout_aspect_1 = layout_right[1,1] = GridLayout(1,1)

Box(fig[1,1], color = :green, width = 200)
Box(layout_aspect_1[1,1], color = :red)
Box(layout_right[2,1], color = :blue)

colsize!(layout_aspect_1, 1, Aspect(1, 1.0))
rowsize!(layout_right, 2, Auto(0.25))

I have dabbled with some hacky ways to get the effect I want, but I wonder if there is a clean and simple solution.

This might not be useful depending on your actual use case…

If in your actual use case you’re not using Box and instead were using Axis, you could use make the Axis have constant aspect ratio instead of changing GridLayout properties:

f = Figure()
g1 = GridLayout(f[1,2])

Box(f[1,1])
Axis(g1[1,1],aspect=AxisAspect(1))
Box(g1[2,1])

colsize!(f.layout,1,Fixed(200))
rowsize!(g1,2,Auto(0.25))

f

Thanks for the input! Unfortunately, all three boxes are actually compound objects in my scenario. The red one, for example, is a heatmap with additional marginal plots and some labels (so three axes in total).

1 Like

So as you’ve noticed colsize! or rowsize! with Aspect has the drawback that it only works if you know which dimension is the longer/shorter one. Otherwise it can cause overlaps. It doesn’t matter for static plots but for interactive ones with unknown figure size it’s a bit impractical.

GridLayoutBase is not a constraint solver, it only follows one fixed algorithm to determine the outcome, that’s why you can’t have it pick the “best” solution or so. But there are still some tricks, for example in your case you can place one GridLayout where you placed the red box. You only need that for its suggestedbbox observable which updates with the grid cell rectangle basically. From that you can compute a square box and pass that as the manual bbox keyword to another GridLayout. I also assign parent here so that Box afterwards knows which Figure the grid belongs to.

using GLMakie

fig = Figure()
layout_right = fig[1,2] = GridLayout(2,1)
layout_aspect_1 = layout_right[1,1] = GridLayout(1,1)

Box(fig[1,1], color = :green, width = 200)
# Box(layout_aspect_1[1,1], color = :red)
Box(layout_right[2,1], color = :blue)

proxy_layout = GridLayout(layout_aspect_1[1, 1])
bbox = lift(proxy_layout.layoutobservables.suggestedbbox) do bb
    w = minimum(widths(bb))
    diff = widths(bb) .- w
    Rect2f(bb.origin .+ diff ./ 2, (w, w))
end

layout_in_square = GridLayout(bbox = bbox, parent = fig)
Box(layout_in_square[1, 1], color = :red)

rowsize!(layout_right, 2, Auto(0.25))

fig

2 Likes

Thanks a lot, this works! And it’s much nicer than my previous attempts at juggling layoutobservables…