Makie.jl icon indicating copy to clipboard operation
Makie.jl copied to clipboard

[FR] subplotgrid function

Open Datseris opened this issue 3 years ago • 11 comments

Would be nice to have something equivalent with PyPlot's subplots function:

fig, axs = subplotgrid((X, Y); sharex, sharey)

such a function would make an XxY grid layout, and optionaly share their x and/or y axis (also hiding ticks if axis are shared).

(I'll do this, assign me on GitHub please)

Datseris avatar Jan 11 '22 14:01 Datseris

@SimonDanisch I'm not making a PR yet because I just don't know where to put this function. But here is the code:

(EDIT: Code updated to be even better!)

using Makie
Text = Union{Symbol, <: AbstractString}

"""
    subplotgrid(m, n; kwargs...) -> fig, axs

Create a grid of `m` rows and `n` columns of axes in a new figure and return the figure and
the `Matrix` of axis.

## Keyword arguments

- `sharex/sharey = false`: make every row share the y axis and/or every row column
  share the x axis. In this case, tick labels are hidden from the shared axes.
- `titles::Vector{String}`: if given, they are used as titles
  for the axes of the top row.
- `xlabels::Vector{String}`: if given, they are used as x labels of the axes
  in the bottom row
- `ylabels::Vector{String}`: if given, they are used as y labels of the axes in the
  leftmost column
- `title::String`: if given, it is used as super-title for the entire figure
  using the `figuretitle!` function.
- `kwargs...`: all further keywords are propagated to `Figure`.
"""
function subplotgrid(m, n;
        sharex = false, sharey = false, titles = nothing,
        xlabels = nothing, ylabels = nothing, title = nothing, kwargs...
    )
    fig = Makie.Figure(; kwargs...)
    axs = Matrix{Axis}(undef, m, n)
    for i in 1:m
        for j in 1:n
            axs[i,j] = Axis(fig[i,j])
        end
    end
    if sharex
        for j in 1:n
            Makie.linkxaxes!(axs[:,j]...)
            for i in 1:m-1; Makie.hidexdecorations!(axs[i,j]; grid = false); end
        end
    end
    if sharey
        for i in 1:m # iterate through rows
            Makie.linkyaxes!(axs[i,:])
            for j in 2:n; Makie.hideydecorations!(axs[i,j]; grid = false); end
        end
    end
    if !isnothing(titles)
        for j in 1:n
            axs[1, j].title = titles[j]
        end
    end
    if !isnothing(xlabels)
        for j in 1:n
            axs[end, j].xlabel = xlabels isa Text ? xlabels : xlabels[j]
        end
    end
    if !isnothing(ylabels)
        for i in 1:m
            axs[i, 1].ylabel = ylabels isa Text ? ylabels : ylabels[i]
        end
    end
    !isnothing(title) && figuretitle!(fig, title)
    return fig, axs
end

"""
    figuretitle!(fig, title; kwargs...)

Add a title to a `Figure`, that looks the same as the title of an `Axis`
by using the same default font. `kwargs` are propagated to `Label`.
"""
function figuretitle!(fig, title;
        valign = :bottom, padding = (0, 0, 0, 0),
        font = "TeX Gyre Heros Bold", # same font as Axis titles
        kwargs...,
    )
    Label(fig[0, :], title;
        tellheight = true, tellwidth = false, valign, padding, font, kwargs...
    )
    return
end

feel free to give a review and/or tell me where to put it.

Datseris avatar Jan 11 '22 22:01 Datseris

image

Datseris avatar Jan 11 '22 22:01 Datseris

bump!

Datseris avatar Jan 16 '22 09:01 Datseris

I will also make an in-place version that takes an existing figure or GridLayout as an argument and adds the axis. Just tell me where to actually put it to make a PR :D

Datseris avatar Apr 17 '22 12:04 Datseris

Maybe this would be good for a Block, it could be called FacetGrid for example. That's because it could benefit from spanning labels on the axes and maybe boxes for the titles such as commonly found in ggplot.

jkrumbiegel avatar Apr 17 '22 13:04 jkrumbiegel

I am not aware of what a Block is (https://makie.juliaplots.org/stable/search/index.html?q=Block ). Is it internal?

Datseris avatar Apr 17 '22 13:04 Datseris

It's the new implementation of what Layoutable was, on master. A building block possibly made of other building blocks, that you can place in a grid as one rectangular object.

jkrumbiegel avatar Apr 17 '22 16:04 jkrumbiegel

Yes, I'll make it a block once I have the instructions of how to do so. Is it just a simple wrap of a GridLayout? In reality, the function I've coded above can just return a GridLayout.

Datseris avatar Apr 17 '22 16:04 Datseris

There's an initial implementation here https://github.com/JuliaPlots/Makie.jl/pull/1827

begin
    f = Figure()
    fg = MakieLayout.FacetGrid(f[1, 1], 3, 4, sharey = :all,
        rowlabels = ["Hi", "Whats", "Up"])
    fg.columnlabels = ["a", "b", "c", "d"]
    lines!(fg[1, 2], cumsum(randn(50)))
    lines!(fg[3, 4], cumsum(randn(50)))
    Label(f[0, :], "A FacetGrid demonstration", tellwidth = false)
    f
end

grafik

I'm not sure yet if I like making an object out of this or if it should just make a bunch of axes wherever and return those. The options to style this are potentially infinite, and it's not going to be practical to implement them all with overlapping keywords etc. For example position of label boxes, if it's a full grid or if there are positions missing, etc. However, using a Block does make the thing somewhat nicely self contained.

jkrumbiegel avatar Apr 18 '22 17:04 jkrumbiegel

I really hope the Makie developers strongly consider adding the above subplotgrid function to the API and exporting it, because it has become rather tedius to copy paste it in 20 different science projects and then include the file with this definition in every one scripts in these projects. Given the prevalence of "facet/grid plots" in science, this function is likely to be widespread used if available.

Datseris avatar Sep 14 '22 10:09 Datseris

Yes, definitely. I need a bit more time to think about how exactly to integrate it, as this function is only one example of a multi-axis (or object) plotting recipe, for which we need better infrastructure anyway. So I don't want to include this as is only to come up with a unified way a bit later, leading to more breaking changes.

jkrumbiegel avatar Sep 14 '22 11:09 jkrumbiegel

I've updated my original post with an even better and more general version of the code, that I Am particularly prooud about! I use this code in literally every science project I have!

Datseris avatar Apr 05 '23 09:04 Datseris

Yeah I agree, let me try to prepare a PR soon, I have some small ideas about the API that I'd want to try. But it would be very useful to have

jkrumbiegel avatar Apr 07 '23 16:04 jkrumbiegel