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

[BUG] Layouts meta-issue #2783

Closed
isentropic opened this issue Jun 13, 2020 · 0 comments
Closed

[BUG] Layouts meta-issue #2783

isentropic opened this issue Jun 13, 2020 · 0 comments

Comments

@isentropic
Copy link
Member

I was trying to crack why Plots layouting does not always work perfectly and stumbled upon the 2016 year Toms issues and how he had in mind in the first place. I believe not many people to this day understand exactly how layouting works.

Perhaps, it would be a good idea to have comprehensive developer docs http://docs.juliaplots.org/latest/pipeline/ and explain concepts thoroughly with linking relevant code. But this is a digression, better documentation is always desired and all packages.

This is relevant to GR and PyPlot any other backend that would use _update_min_padding! callback. Upon conservation with @BeastyBlacksmith I learned that pgfplotsx handles this a little differently

First of all, let us start with an example

p1 = plot(rand(10));
p2= plot(rand(10)); 
p3 = plot(rand(10)); 
p4 = plot(rand(10));

plot(p1,p2,p3,p4)  # looking well and good

image

p1 = plot(rand(10), title="title", ylabel="label");
p2= plot(rand(10)); 
p3 = plot(rand(10)); 
p4 = plot(rand(10));

plot(p1,p2,p3,p4)  # looking good too!

image

This example is a typical MWE when layouting fails:

p1 = plot(rand(10));
p2= plot(rand(10)); 
p3 = plot(rand(10)); 
p4 = plot(rand(10), title="title", ylabel="label", xlabel="xlabel");

plot(p1,p2,p3,p4)  # p4 layouting fails miserably

Note how p4 is not aligned at all
image

Issues reporting the same things: (perhaps should be deleted to keep things cleaner):
#2237
#1947

Why this happens:
This code is responsible for updatting the padding of near-lying subplots, when a subplot contains sticking out elements (legends, colorbars, guides).

That is, every neighbor subplot updates its padding so the axes spines (plotarea) is aligned within each other. For example,
image
Look how 1 plot got additional padding so the axes align well.

The code (verbatim) says:

function _update_min_padding!(layout::GridLayout)
    map(_update_min_padding!, layout.grid)
    layout.minpad = (
        maximum(map(leftpad,   layout.grid[:,1])),
        maximum(map(toppad,    layout.grid[1,:])),
        maximum(map(rightpad,  layout.grid[:,end])),
        maximum(map(bottompad, layout.grid[end,:]))
    )
end

That is each plot should have maximal padding taken among its neighbors so that each plot has aligned spines (plotarea).

Then at the second dive into subplots, margins layout nicely (deleted insets for clarity):

function prepare_output(plt::Plot)
    _before_layout_calcs(plt)

    w, h = plt.attr[:size]
    plt.layout.bbox = BoundingBox(0mm, 0mm, w*px, h*px)

    # One pass down and back up the tree to compute the minimum padding
    # of the children on the perimeter.  This is an backend callback.
    _update_min_padding!(plt.layout)    # calculate the boxes for each subplot

    # now another pass down, to update the bounding boxes
    update_child_bboxes!(plt.layout)    # update the boxes so that everything aligns well

    # the backend callback, to reposition subplots, etc
    _update_plot_object(plt)
end

The problem here

function _update_min_padding!(layout::GridLayout)
    map(_update_min_padding!, layout.grid)
    layout.minpad = (
        maximum(map(leftpad,   layout.grid[:,1])),
        maximum(map(toppad,    layout.grid[1,:])),
        maximum(map(rightpad,  layout.grid[:,end])),
        maximum(map(bottompad, layout.grid[end,:]))
    )
end

is that the boxes are only updated for left top bottom right (outer borders)

Boxes inside the grids are not updated. This only aligns tops, bottoms, lefts, rights of a regular (2x2) grid. But it would never align the inside parts like in this example:

p1 = plot(rand(10));
p2= plot(rand(10)); 
p3 = plot(rand(10)); 
p4 = plot(rand(10), title="title", ylabel="label", xlabel="xlabel");

plot(p1,p2,p3,p4)  # p4 layouting fails miserably

Note how p4 is not aligned at all
image
So the 4th plot left top bounds do update neighbors as recursion never reaches there, simply because grid layout only cares about top left bottom right.

But it works fine with

p1 = plot(rand(10), title="title", ylabel="label");
p2= plot(rand(10)); 
p3 = plot(rand(10)); 
p4 = plot(rand(10));

plot(p1,p2,p3,p4)  # looking good too!

image

Because, here only top and left needs to be aligned. ( not inside)

Hence, grid layout margins should be aligned from inside too.

One way to this is to have subgrids of grids, so that the same alignment is repeated over each row and each column of the grid. (not only outer borders)
The solution lays in rethinking

function _update_min_padding!(layout::GridLayout)
    map(_update_min_padding!, layout.grid)
    layout.minpad = (
        maximum(map(leftpad,   layout.grid[:,1])),
        maximum(map(toppad,    layout.grid[1,:])),
        maximum(map(rightpad,  layout.grid[:,end])),
        maximum(map(bottompad, layout.grid[end,:]))
    )
end

So that maximum also accounts for central alignment, not only sides.

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

1 participant