Makie plots with equal height + unequal width + data aspect ratio

I’d like to have a few plots arranged in a row that have both a data aspect ratio and equal heights, and therefore unequal widths. I can’t seem to get it to work no matter what combination of DataAspect, height, limits, autolimitaspect, tellhegith, etc I use.
Here’s the MWE:

n = 100
scene, layout = layoutscene(resolution = (1000, 300))
spans = range(0.2, 1, length = 5)
for (i, span) in enumerate(spans)
  x = rand(n) .* span
  # y = rand(n) .* span
  ax = layout[1, i] = Axis(scene, aspect = DataAspect(),  height = 200, limits = (extrema(x)..., 0.0, 1.0))
  # lines!(ax, x, y)
end
# rowsize!(layout, 1, Fixed(200))
scene

(I intentionally commented out stuff that shouldn’t make a difference)
Here’s the result (note the unequal heights):


I’m on GLMakie v0.2.9.

1 Like

Ok so the height should be the same but the width change given the data aspect? That means the plot size is completely deterministic and needs to fit the figure size exactly. So probably you should choose a height, plot your data and check the ax.finallimits of each axis, set each axis height and width in the correct ratio given the limits, then do the figure resize thing I posted on Slack the other day.

I’ll try to set the width and height manually then.

slack hole, or can’t find it.

But I’ll try what you suggested now and report back here :slight_smile:

OK, so this works well enough:

height = 200
ym = 0.0
yM = 1.0
n = 100
scene, layout = layoutscene(resolution = (1000, 300))
spans = range(0.2, 1, length = 5)
for (i, span) in enumerate(spans)
  x = rand(n) .* span
  xm, xM = extrema(x)
  y = rand(n) .* span
  ax = layout[1, i] = Axis(scene, width = height/(yM - ym)*(xM - xm),  height = height, limits = (xm, xM, ym, yM))
  lines!(ax, x, y)
  poly!(ax, Circle(Point2f0((xM - xm)/2, (yM - ym)/2), 0.2), color = :red)
end
scene

The red disk is just to check the aspect ratio, indeed it’s correct:

Let me know if this isn’t a Makie-approved way…

this should go into the Documentation. :smiley:

The lesson here is a bit weird:
“When things don’t work out as expected with aspect ratio, heights, etc just manually set the axis size.”
I’m not sure Jules would be ok with that…?

You could think about it this way. Given a global resolution, there needs to be a procedure to find optimal axis sizes that respect the constraints. This may not have an exact solution for the given resolution, so you might “flip” the problem: you pass the axis sizes and let MakieLayout figure out what overall resolution will come out.

The code Julius refers to is something like the following (taken from his slack snippet):

using GridLayoutBase: Col, Row, determinedirsize

function resizetocontent!(fig::Figure)
    figsize = size(fig.scene)
    sz = map((Col(), Row()), figsize) do dir, currentsize
        inferredsize = determinedirsize(fig.layout, dir)
        return ceil(Int, something(inferredsize, currentsize))
    end
    sz == figsize || resize!(fig.scene, sz)
    return fig
end

That is to say, if the resolution can be computed starting from the layout (and is different from the resolution initially chosen) resize the figure. Some utility like this could probably live in Makie.

Thanks!

I appreciate the complexity of these procedure. It’s pretty tricky! I guess that down the line it would be good to have some more documentation about these more complicated layouts.

The problem here is again that the layout cannot use the information that objects want to have a certain aspect ratio. If it could then we would have an optimization problem, while the layout follows a deterministic algorithm. If you give an axis aspect = DataAspect() that means that it will reduce its frame from the available space so that the visual aspect ratio is the same as that of the data. This will leave gaps if the available space doesn’t perfectly fit the required size already.

You can either go all the way inside out and fix the axis sizes completely, then resize the figure to fit as in @piever’s snippet.

Or you can say, well at least if the axes shrink down, they should do so in a way that they end up with the same height. (If you could fix the height and enforce aspect of each axis, you would immediately have to have a certain fixed width per axis, so that would again break the layout if the widths didn’t add up perfectly by chance.)

So you can reach this goal by making the layout size all columns with correct relative ratios, given the xlimits of your axes. You do that by setting their width to Auto(span), which will distribute the available space according to the size of each span. Then all axes have to reach the same height if they resize themselves using DataAspect().

scene, layout = layoutscene(resolution = (1000, 400))
spans = range(0.2, 1, length = 5)
for (i, span) in enumerate(spans)
    ax = layout[1, i] = Axis(scene, aspect = DataAspect(),
        limits = (0, span, 0.0, 1.0))
    poly!(ax, Circle(Point2f0(span / 2, 0.5), 0.1))
    colsize!(layout, i, Auto(span))
end
scene

3 Likes

I thank @jules for providing this elegant solution. Using this method, I was able to create a figure with 2×2 panels with matched widths and heights while using Axis(...; aspect=DataAspect()).

What was not clear to me was the meaning of colsize!(). It is obvious that it means the column size, but again, what does the column size mean? Initially I thought it meant the height of the column, but it turned out to mean the width of the column. It took a couple of experiments to figure this out. Similarly, rowsize!() is not the width of the row, but the height of the row.

I think the documentation can be improved by explicitly mentioning whether the size in rowsize!() and colsize!() means width or height.

In the GridLayout, column sizes are widths and row sizes are heights. The height of a column is simply computed as the sum of the row widths plus gaps, you can’t set it directly.