Axis-generating functions in Makie

I would like to organize some commonly used plots into functions. Eg

function makie_plot_posterior_check(figure, label, data, predicted;
                                    scatter_color = alphacolor(colorant"black", 0.7),
                                    diagonal_color = colorant"gray")
    ax = Makie.Axis(figure; xlabel = "$(label) data", ylabel = "$(label) (posterior mean)")
    Makie.ablines!(ax, [0], [1]; color = diagonal_color)
    Makie.scatter!(ax, data, predicted; color = scatter_color)
    figure
end

which I would then use as eg

f = Figure()
makie_plot_posterior_check(f[1,1], "male work hours", data.n1, predicted.n1)
save("output_path.svg", f)

The idea is that I may make a single plot of this kind, or a grid of them, etc, using this function.

But I find it tedious to have to pass in the figure/scene.

Is it possible/idiomatic to create an Axis in Makie, then later on attach it to a Figure (or scene)?

Or am I organizing my code the wrong way?

1 Like

This is not (yet) supported. Or is it @jules ?


I wonder if it would be possible to create a Lazy<:Makie.Block that allows to wrap any other Block with

lazyax = Lazy(Axis, kwargs...)
# or maybe with a macro
lazyax = @lazy Axis(kwargs ...)

and that when inserted into a GridLayout resolves all the “recorded” plotting calls?

Assuming that could work, the code could be simplified to

function makie_plot_posterior_check(label, data, predicted;
                                    scatter_color = alphacolor(colorant"black", 0.7),
                                    diagonal_color = colorant"gray")
    ax = @lazy Makie.Axis(xlabel = "$(label) data", ylabel = "$(label) (posterior mean)")
    Makie.ablines!(ax, [0], [1]; color = diagonal_color)
    Makie.scatter!(ax, data, predicted; color = scatter_color)
    ax
end

f = Figure()
f[1,1] = makie_plot_posterior_check("male work hours", data.n1, predicted.n1)
save("output_path.svg", f)

Attaching an Axis (or any plot object) to a Figure/Scene after creation is not possible currently.

What I do currently, and which we haven’t had time to consolidate into a real recipe-like API is something like this, where you can plot your recipe into any gridlayout position. If no Figure exists, yet, it is created automatically. It would be nice to have this at some point with full tie-in to the theming system, conversion system, etc.

In this example, you only need to define compositeplot once and can reuse it for all plotting functions, then one forwarding method of the plotting function and one implementation method.

There are still things to work out, like what each method should return etc. but it might be a start for you.

using CairoMakie

function compositeplot(func::Function, gp::Union{GridPosition,GridSubposition}, args...; kwargs...)
    f = Makie.get_top_parent(gp)
    f isa Figure || error("Parent of GridPosition must be a Figure not a $(typeof(f))")
    g = Makie.GridLayoutBase.get_layout_at!(gp; createmissing = true)
    result = func(f, g, args...; kwargs...)
    return f, result
end

function compositeplot(func::Function, args...; figure = (;), kwargs...)
    f = Figure(; figure...)
    result = func(f[1, 1], args...; kwargs...)
    return f, result
end

myplot(args...; kwargs...) = compositeplot(myplot, args...; kwargs...)

function myplot(f::Figure, g::GridLayout, data::Vector{Float64}; colormap = :RdBu)
    ax = Axis(g[1, 1], title = "$colormap")
    sc = scatter!(ax, data; color = randn(length(data)), colormap)
    cb = Colorbar(g[2, 1], sc, vertical = false)
    return (; axis = ax, colorbar = cb, scatterlines = sc)
end

fig, stuff = myplot(cumsum(randn(100)))
myplot(fig[2, 1], cumsum(randn(100)); colormap = :inferno)
_, morestuff = myplot(fig[1:2, 2], cumsum(randn(100)); colormap = :Blues)
morestuff.colorbar.label = "Hi from the label"
fig

2 Likes