How to stick a colorbar to a scaled axis in Makie

Here is a MWE of a Makie plot with a wide figure with one square axis and a colorbar “attached” to it:

f = Figure(resolution=(900,300))
Axis(f[1, 1], aspect = AxisAspect(1))
Box(f[1, 1], color = (:blue, 0.2), strokecolor = :transparent)
Colorbar(f[1, 2])

which gives

As you can see the colorbar is quite far because the bounding box (indicated in blue) is quite wide (because it “fills” the figure size). While this might be OK for a figure with a single panel, it is not so great when there is another panel to the right. MWE #2:

f = Figure(resolution=(1200,300))
ga = f[1,1] = GridLayout()
Axis(ga[1, 1], aspect = AxisAspect(1))
Colorbar(ga[1, 2])
gb = f[1,2] = GridLayout()
Axis(gb[1, 1])

where we create two panels (GridLayouts) where the left one has the colorbar. However, this gives

where the colorbar looks like it is attached to the right panel instead of the left one. So the question is: How does one “glue” the colorbar to the axis in the left panel?

1 Like

One solution (thanks to @jules via Slack) is to set the square aspect of the left axis through colsize! instead of when creating the axis:

f = Figure(resolution=(1200,300))
ga = f[1,1] = GridLayout()
Axis(ga[1, 1])    # no AxisAspect needed here anymore
Colorbar(ga[1, 2])
gb = f[1,2] = GridLayout()
Axis(gb[1, 1])
colsize!(ga, 1, Aspect(1, 1))    # <- does the job

which gives

But maybe there is a better way?

(Posting this here was a good idea, thanks @Datseris!)

1 Like

If you don’t wrap the Aspect column in a nested GridLayout, the other Axis can expand to the remaining width. As it is, ga takes half the width because the size restriction from Aspect doesn’t propagate outwards as a known width. (And I’m not sure if it’s possible to change that behavior with the current algorithm).

f = Figure(resolution=(1200,300))
Axis(f[1, 1])    # no AxisAspect needed here anymore
Colorbar(f[1, 2])
Axis(f[1, 3])
colsize!(f.layout, 1, Aspect(1, 1))    # <- does the job
f

3 Likes

what about the first case, just one square and one colorbar? without f[1,3] but still with the colorbar very close to the square. And it must be at the centre. Using the above approach everything is shifted to the left.

1 Like

To centre everything I guess a workaround is to add invisible axes to the left and right. For the two-panel case:

f = Figure(resolution=(1200,300))
ga = f[1,1] = GridLayout()
Axis(ga[1, 2])
Colorbar(ga[1, 3])
invisax = Axis(ga[1, 4])
hidespines!(invisax)
hidedecorations!(invisax)
gb = f[1,2] = GridLayout()
Axis(gb[1, 1])
colsize!(ga, 2, Aspect(1, 1))

And same workaround for the single panel case:

f = Figure(resolution=(900,300))
Axis(f[1, 2], aspect = AxisAspect(1))
Colorbar(f[1, 3])
invisax = Axis(f[1, 4])
hidespines!(invisax)
hidedecorations!(invisax)

I came here for the same reasons, but wanted to mention that the solution that worked best for me (albeit not for the exact same purpose, but similar) was Julius’ solution:

MWE

Here, I plot a bunch of plots with circles on them, their limits are not the same, but their aspect is dictated by the data (and thus the circles are perfectly round), and still the label is right below them:

fig = Figure()
axs = [Axis(fig[1, i], aspect = DataAspect()) for i in 1:5]
foreach(axs) do ax
  lines!(ax, Circle(Point2f0(0,0), 1))
  scatter!(ax, 2randn(Point2f0, 5))
end
Label(fig[2,:], "Look, I'm right below the data-aspect plots")
rowsize!(fig.layout, 1, Aspect(1,1))

I guess, apart from perhaps the documentation Julius wanted to add, one remaining issue is the dead space below the label. Maybe there is a neat way to trim that (apart from imagemagick etc)?

1 Like

In GridLayoutBase 0.6.2, gridlayouts respect their alignments when they don’t reach the exact size that they should. So the example is now centered by default:

f = Figure(resolution=(1200,300))
Axis(f[1, 1])
Colorbar(f[1, 2])
colsize!(f.layout, 1, Aspect(1, 1))
f

Also, with that rewrite I can soon add a function like gridlayoutsize or something to shrink the figure to the actually required size of the gridlayout, so if I manually add the outer padding just for this example, I get:

resize!(f.scene, gridlayoutsize(f.layout) .+ (32, 32))
f

grafik

1 Like

And same with this example

set_theme!(theme_black())
fig = Figure()
axs = [Axis(fig[1, i], aspect = DataAspect()) for i in 1:5]
foreach(axs) do ax
  lines!(ax, Circle(Point2f(0,0), 1))
  scatter!(ax, 2randn(Point2f, 5))
end
Label(fig[2,:], "Look, I'm right below the data-aspect plots")
rowsize!(fig.layout, 1, Aspect(1,1))
fig
##
resize!(fig.scene, gridlayoutsize(fig.layout) .+ (32, 32))
fig
2 Likes

I love the new improvement which gives a centred plot. But, I wonder, if when doing this resize! thing the relative font size will also change (as I can see the fig shown), that would not be good because when including figures into a document it makes sense to have always the same font size for all of them which I usually achieve by keeping the resolution and font sizes fixed.

No that’s just a combination of Vscode and discourse scaling the different image, the font sizes etc are exactly the same. Just the empty space is removed.

1 Like

Wow, be sure to ping (here?) when said function comes online. I’d love to use it in my current analyses.

1 Like

Julius did it: Aspect ratio and size control tutorial

4 Likes

I’ve also struggled finding the correct logic for an horizontal colorbar, so here is a toy code for sticking colorbar to a data aspect ratio scaled axis, and maintaining a figure resolution close to defaults:

using CairoMakie

meshgrid(x, y) = repeat(x, 1, length(y)), repeat(y', length(x), 1)

aspect_ratio(x, y) = begin
  # see docs.makie.org/stable/examples/blocks/axis/index.html#aspect
  (mx, Mx), (my, My) = extrema(x), extrema(y)
  Δx, Δy = Mx - mx, My - my
  Δx / Δy, Δx > Δy
end

main() = begin
  xy = [1:450, 101:250]
  for vertical in (true, false), (x, y) ∈ (xy, reverse(xy))
    nx, ny = length(x), length(y)
    ar, wider = aspect_ratio(x, y)
    @show ar wider
    fig = Figure()
    if wider
      rowsize!(fig.layout, 1, Aspect(1, 1 / ar))
    else
      colsize!(fig.layout, 1, Aspect(1, ar))
    end
    ax = Axis(fig[1, 1]; title="aspect ratio", aspect=DataAspect())
    hm = heatmap!(ax, meshgrid(x, y)..., rand(nx, ny))
    Colorbar(vertical ? fig[1, 2] : fig[2, 1], hm; vertical, flipaxis=false)
    resize_to_layout!(fig)
    save("aspect-$nx×$ny-$(vertical ? "vert" : "horz").png", fig; px_per_unit=2)
  end
end

main()