Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/y-lims are not updated when fitting subplots into layout #3633

Open
anneaux opened this issue Jul 8, 2021 · 25 comments
Open

x/y-lims are not updated when fitting subplots into layout #3633

anneaux opened this issue Jul 8, 2021 · 25 comments

Comments

@anneaux
Copy link

anneaux commented Jul 8, 2021

Hey!
When I fit multiple subplots with equal aspect ratios into a layout, then their axis may be extended in order to fit the layout. However, the values for xlims and ylims are not being updated. Here's a MWE:

using Plots; gr()
using Random; Random.seed!(1234)

p = plot(layout = grid(1,2), frame=:box)

y1 = rand(10) .* 10
plot!(sp=1, y1, aspect_ratio=:equal) # subplot 1, i.e. p[1]

y2 = rand(5) .* 10
plot!(sp=2, y2, aspect_ratio=:equal) # subplot 2, i.e. p[2]

This produces this:
plot1

and I get the following values:

extrema(y1) = (2.005860349338411, 8.541465903790503)
Plots.xlims(p[1]) = (0.73, 10.27)
Plots.ylims(p[1]) = (1.809792182704848, 8.737534070424065)
extrema(y2) = (0.10905889635595356, 9.567533636029237)
Plots.xlims(p[2]) = (0.88, 5.12)
Plots.ylims(p[2]) = (-0.17469534583424495, 9.851287878219436)

You see, for example, that the y axis of subplot 1 (the left one) goes from somewhere -1 to ~12 (ignore the cutted ticks label....), but the according ylims(p[1]) gives roughly (1.8,8.7). The same is obvious for subplot 2, where the x-axis ranges from -0.5 to 6.5 roughly, but xlims(p[2]) gives (0.88,5.12).

So... how can one get the actual limits of the axis after they have been fitted into the layout?

Thanks in advance!

(I'm using Julia v1.6.1, Plots v1.18.0)

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

Could you please re-run (for the values and images: I've modified your example to use a fixed seed, for reproducibility) ?

Ok I seem to reproduce:

extrema(y1) = (2.005860349338411, 8.541465903790503)
Plots.xlims(p1) = (0.73, 10.27)
Plots.ylims(p1) = (1.809792182704848, 8.737534070424065)
extrema(y2) = (0.10905889635595356, 9.567533636029237)
Plots.xlims(p2) = (0.73, 10.27)
Plots.ylims(p2) = (1.809792182704848, 8.737534070424065)  # NOK

I'm suspecting 6f44004, but it got in after 1.18.0 ...

@t-bltg t-bltg added the bug label Jul 8, 2021
@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

Stack triggering incorrect values:

┌ Debug: 
│   exception =14-element Vector{Base.StackTraces.StackFrame}:macro expansion at logging.jl:341 [inlined]
│     axis_limits(sp::Plots.Subplot{Plots.GRBackend}, letter::Symbol, should_widen::Bool, consider_aspect::Bool) at axes.jl:557axis_limits(sp::Plots.Subplot{Plots.GRBackend}, letter::Symbol) at axes.jl:557
│     get_sp_lims at utils.jl:420 [inlined]
│     ylims(sp::Plots.Subplot{Plots.GRBackend}) at utils.jl:435ylims(plt::Plots.Plot{Plots.GRBackend}, sp_idx::Int64) at utils.jl:445ylims(plt::Plots.Plot{Plots.GRBackend}) at utils.jl:445
│     top-level scope at show.jl:955
│     eval at boot.jl:360 [inlined]
│     include_string(mapexpr::typeof(identity), mod::Module, code::String, filename::String) at loading.jl:1094_include(mapexpr::Function, mod::Module, _path::String) at loading.jl:1148include(mod::Module, _path::String) at Base.jl:386exec_options(opts::Base.JLOptions) at client.jl:285_start() at client.jl:485
└ @ Plots [...]/Plots/CoJwR/src/axes.jl:557

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

So... how can one get the actual limits of the axis after they have been fitted into the layout?

You should use xlims(p, 2): this might seem counter-intuitive but plot!(sp=2, ...) does not return the second subplot, but the main plot a.k.a p.

using Plots; gr()
using Random; Random.seed!(1234)

p = plot(layout=grid(1, 2), frame=:box)

y1 = rand(10) .* 10
plot!(sp=1, y1, aspect_ratio=:equal)
@show extrema(y1)
@show xlims(p, 1)
@show ylims(p, 1)

y2 = rand(5) .* 10
plot!(sp=2, y2, aspect_ratio=:equal)
@show extrema(y2)
@show xlims(p, 2)
@show ylims(p, 2)

Output:

extrema(y1) = (2.005860349338411, 8.541465903790503)
xlims(p, 1) = (0.73, 10.27)
ylims(p, 1) = (1.809792182704848, 8.737534070424065)
extrema(y2) = (0.10905889635595356, 9.567533636029237)
xlims(p, 2) = (0.88, 5.12)
ylims(p, 2) = (-0.17469534583424495, 9.851287878219436)

@t-bltg t-bltg closed this as completed Jul 8, 2021
@t-bltg t-bltg removed the bug label Jul 8, 2021
@anneaux
Copy link
Author

anneaux commented Jul 8, 2021

I am sorry to say that, but that does not fix my problem 🙈 you see the values for ylims(p,1)? They still don't match the actual axes limits in the picture!

@t-bltg t-bltg reopened this Jul 8, 2021
@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

I am sorry to say that, but that does not fix my problem see_no_evil you see the values for ylims(p,1)? They still don't match the actual axes limits in the picture!

I missed that, sorry ! Let's dig down further, this might be backend dependent ...

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

With pgfplotsx:
eee

So with gr is think a margin issue explains with the 1 in 10.0 is chopped out.
You can use p = plot(layout=grid(1, 2), frame=:box, margins=3Plots.mm) for fixing that.

But it still doesn't explain why the ylims are incorrect ...

@anneaux
Copy link
Author

anneaux commented Jul 8, 2021

yes, I don't mind the margins for now ^^
It happens for xlims as well (I edited this for clarification in the first post)

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

yes, I don't mind the margins for now ^^
It happens for xlims as well (I edited this for clarification in the first post)

Ok, so what is not reflected on the plots is:

extrema(y1) = (2.005860349338411, 8.541465903790503)
xlims(p, 1) = (0.73, 10.27)
ylims(p, 1) = (1.809792182704848, 8.737534070424065)  # <== NOK
extrema(y2) = (0.10905889635595356, 9.567533636029237)
xlims(p, 2) = (0.88, 5.12)  # <== NOK
ylims(p, 2) = (-0.17469534583424495, 9.851287878219436)

right ?

Something is swapped somewhere in the code ...

@anneaux
Copy link
Author

anneaux commented Jul 8, 2021

exactly!

It seems the axis range in the final plot is determined from either of the two axis and then adjusts the other one accordingly. So that's why always one of them is correctly (x or y)

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

The aspect_ratio is responsible here, it should be too hard to find the issue:

using Plots; gr()
using Random; Random.seed!(1234)

p = plot(layout=grid(1, 2), frame=:box, margins=3Plots.mm)

y1 = rand(10) .* 10
plot!(sp=1, y1)

y2 = rand(5) .* 10
plot!(sp=2, y2)

gives
eee

which is correct.

@anneaux
Copy link
Author

anneaux commented Jul 8, 2021

Ok, I am sorry, maybe I was a little confusing...

I need the plots to keep the aspect ratio, and with the plotting itself I am totally fine!

But, what I want are just the exact values for the axis limits as they are in the final plot.
E.g. for relative positioning of annotations as mentioned in issue #2728

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

Ok, I am sorry, maybe I was a little confusing...

I need the plots to keep the aspect ratio, and with the plotting itself I am totally fine!

But, what I want are just the exact values for the axis limits as they are in the final plot.
E.g. for relative positioning of annotations as mentioned in issue #2728

This seems to work, triggering rendering of the image before querying the axes limits.

using Plots; gr()
using Random; Random.seed!(1234)

p = plot(layout=grid(1, 2), frame=:box, margins=3Plots.mm)

y1 = rand(10) .* 10
plot!(sp=1, y1, aspect_ratio=:equal)
@show extrema(y1)
@show xlims(1)
@show ylims(1)

y2 = rand(5) .* 10
plot!(sp=2, y2, aspect_ratio=:equal)
@show extrema(y2)
@show xlims(2)
@show ylims(2)

# show(devnull, "image/png", p)
Plots.prepare_output(p)

println()
@show xlims(1)
@show ylims(1)

@show xlims(2)
@show ylims(2)

Output:

extrema(y1) = (2.005860349338411, 8.541465903790503)
xlims(1) = (0.73, 10.27)
ylims(1) = (1.809792182704848, 8.737534070424065)
extrema(y2) = (0.10905889635595356, 9.567533636029237)
xlims(2) = (0.88, 5.12)
ylims(2) = (-0.17469534583424495, 9.851287878219436)

xlims(1) = (0.73, 10.27)
ylims(1) = (-1.4413732345950825, 11.988699487723995)
xlims(2) = (-0.5609591226753916, 6.560959122675392)
ylims(2) = (-0.17469534583424495, 9.851287878219436)

Still seems like a bug to me.

@anneaux
Copy link
Author

anneaux commented Jul 8, 2021

ohhh thank you! That's indeed a good workaround!

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

The root of this bug is that we get plot_ratio = NaN here

plot_ratio = height(plotarea(sp)) / width(plotarea(sp))
because of a [0, 0, 0, 0] bbox, hence the ylims are not accounting for the :equal aspect ratio.

@BeastyBlacksmith
Copy link
Member

BeastyBlacksmith commented Jul 8, 2021

because of a [0, 0, 0, 0] bbox, hence the ylims are not accounting for the :equal aspect ratio.

How did you measure that? bbox gives you zeros, if you use it after the fact, but when you add some @shows in the line before or above you'd see that they should be non-zero

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

because of a [0, 0, 0, 0] bbox, hence the ylims are not accounting for the :equal aspect ratio.

How did you measure that? bbox gives you zeros, if you use it after the fact, but when you add some @shows in the line before or above you'd see that they should be non-zero

Taking this example:

using Plots; gr()
using Random; Random.seed!(1234)

p = plot(layout=grid(1, 2), frame=:box, margins=3Plots.mm)

y1 = rand(10) .* 10
plot!(sp=1, y1, aspect_ratio=:equal)
@show extrema(y1)
@show xlims(1)
@show ylims(1)

Sorry an all zero plotarea.

Adding @show plot_ratio, plotarea(sp) after the line quoted before shows a bunch of:

extrema(y1) = (2.005860349338411, 8.541465903790503)
(plot_ratio, plotarea(sp)) = (NaN, BBox{l,t,r,b,w,h = 0.0mm,0.0mm, 0.0mm,0.0mm, 0.0mm,0.0mm})
(plot_ratio, plotarea(sp)) = (NaN, BBox{l,t,r,b,w,h = 0.0mm,0.0mm, 0.0mm,0.0mm, 0.0mm,0.0mm})
xlims(1) = (0.73, 10.27)
(plot_ratio, plotarea(sp)) = (NaN, BBox{l,t,r,b,w,h = 0.0mm,0.0mm, 0.0mm,0.0mm, 0.0mm,0.0mm})
(plot_ratio, plotarea(sp)) = (NaN, BBox{l,t,r,b,w,h = 0.0mm,0.0mm, 0.0mm,0.0mm, 0.0mm,0.0mm})
ylims(1) = (1.809792182704848, 8.737534070424065)

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

And adding this

if isnan(plot_ratio)
    prepare_output(sp.plt)
    plot_ratio = height(plotarea(sp)) / width(plotarea(sp))
end

segfaults with a StackOverflowError induced by an infinite recursion call of axis_limits in _update_min_padding!, so I'm kinda stuck here.

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

We could also change get_sp_lims in utils.jl to:

function get_sp_lims(sp::Subplot, letter::Symbol)
    !(sp[:aspect_ratio]  (:none, :auto)) && prepare_output(sp.plt)  # Issue #3633
    axis_limits(sp, letter)
end

@BeastyBlacksmith, do you think it's worth a PR ?

@BeastyBlacksmith
Copy link
Member

I don't think that is the right way to fix this. There is some calculation in prepare_output that should move upwards in the pipeline such that xlims(sp) gives the correct limits regardless of the aspect_ratio

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

I don't think that is the right way to fix this. There is some calculation in prepare_output that should move upwards in the pipeline such that xlims(sp) gives the correct limits regardless of the aspect_ratio

The problem is _update_min_padding! which is backend dependent. Only using update_child_bboxes!, even if moved before in the pipeline doesn't fix this.

@BeastyBlacksmith
Copy link
Member

why is it a problem that it is backend dependent?

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

why is it a problem that it is backend dependent?

Because then you cannot fix #3633 (comment) which is not backend dependent (no show, savefig, display, ...).

@BeastyBlacksmith
Copy link
Member

There are quite a few functions in the pipeline before show that are backend dependent (all the ones in templates/backend.jl) though, not every backend defines all of them

@BeastyBlacksmith
Copy link
Member

I agree that you should get the right answer independent of the backend, but in general it would be fine to get a different answer for different backends (probably not in this particular case)

@t-bltg
Copy link
Member

t-bltg commented Jul 8, 2021

I agree that you should get the right answer independent of the backend, but in general it would be fine to get a different answer for different backends (probably not in this particular case)

Yes it is normal to get a different answer for a different backend. However I believe that the issue exposed in the mwe #3633 (comment) should be fixed, we can't leave it like this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants