Makie.jl keyword argument recipes (v0.24)

I would like to request some help updating some of my plotting recipes to the new v0.24 style. I know the issue lies with the new attribute system and the computational graph (and no longer being able to manipulate it like a Dict), but I can’t figure out how to make them work.

For reference, I’m doing something in-between a simple argument conversion recipe and a full-blown recipe. I’m not defining a new plotting function, but I am needing something more complex than argument conversion, so that I can pass through keyword arguments.

Minimum working example:

struct Foo
       x::Vector{Float64}
       y::Vector{Float64}
       color::Symbol
end

Makie.convert_arguments(::PointBased, foo::Foo) = (foo.x, foo.y)

# I can plot x vs y like this, but it does not set the color automatically
foo = Foo(1:10, 1:10, :red)
scatter(foo)

# therefore, I overload the plot! function, like a full @recipe would
function Makie.plot!(plot::Plot(Foo))
    map!(plot.attributes, [:arg1], [:scatter, :color]) do foo
        return (foo, foo.color)
    end
    scatter!(plot, plot.attributes, plot.scatter, color=plot.color)
end

# I can plot x vs y with correct color like this
plot(foo)

# however, I can't figure out how to make this work
plot(foo, markersize=10)

In the past, keyword arguments would get passed through, but it seems now that they aren’t. I could also manipulate the dictionary of attributes in my plot! function (could check to see if certain arguments were set, overwrite them, etc…); I don’t need that currently, but it could also be nice to know how to do that.

I think the issue stems from Recipes | Makie noting that the child plot will pick out the relevant attributes, but I’m perhaps not setting them for my overloaded Plot(foo) type. I tried the following, but it did not work:

Makie.documented_attributes(::Type{Plot(Foo)}) = Makie.documented_attributes(Scatter)

so tl;dr, in Makie v0.24, what is the correct way to inherit default attributes and pass them through to the child for an overloaded plot! function? Thanks!

Try a simpler approach, without overloading plot!:

Makie.convert_arguments(::Type{Scatter}, foo::Foo) =
    Makie.SpecApi.Scatter(foo.x, foo.y; foo.color)
1 Like

sounds like Unable to pass attributes if re-directed by recipe · Issue #3904 · MakieOrg/Makie.jl · GitHub ?

1 Like

Thanks for the suggestion! I had no idea that API existed, and it seems perfect for what I need. How can I make it work in 3D though?

struct Bar
       x::Vector{Float64}
       y::Vector{Float64}
       z::Vector{Float64}
       color::Symbol
end

Makie.convert_arguments(::Type{Scatter}, bar::Bar) =
    [Makie.SpecApi.Scatter(bar.x, bar.y, bar.z; color=bar.color)]
Makie.plottype(::Bar) = Scatter
Makie.preferred_axis_type(::Plot(Bar)) = LScene

bar = Bar(1:10, 1:10, 1:10, :red)
plot(bar)

It seems that preferred axis type isn’t taking effect. It may be possible to create the LScene in the spec, but I’d like to be able to plot into existing axes (if I have multiple Bars for example)

Yes, that does seem related. I noticed the same initial issue, so I switched to just passing in the plot.attributes directly, since the documentation stated the child plot would only use the ones that are applicable to it (or at least I interpreted it to say that)

When set used_attributes like you did:

Makie.used_attributes(::Type{Plot{plot}}, ::Foo) = (:markersize, )

I can get the intended functionality with my original plot! overload. However, if I don’t specify it in the plot call, it is set to nothing and does not plot correctly.

So it seems the current issue with that approach is that I need a way to set defaults (ideally inheriting most of them from the child I call). However, I tried several variants of using documented_attributes, plot_attributes, default_attribute_values, default_theme, which appear in the @recipe source and couldn’t figure how to get them called when plotting.

I figured out how to make this work. I embed the following in my plot! overload. It parses the keywords (available in plot.kw), determines if they’re relevant (meaning I set a default), does default substitution, and adds them to the compute graph.

Here is an example for a Surface recipe:

    # set default arguments and parse keywords
    default_kwargs = Dict(
        :shininess => 0f0,
        :specular => 0.0,
        :ssao => true,
        :alpha => 1.0)
    for (key, val) in default_kwargs
        if key in keys(plot.kw)
            val = plot.kw[key]
        end
        Makie.add_input!(plot.attributes, key, val)
    end

I think the issue stems from the fact that default arguments are set per-function (meaning plot, surface, myhist), not per-type (meaning Type{Plot(Foo)} or Type{Plot(Bar)}). So there is nowhere for me to overload a function to tie into existing mechanims for default argument substitution since I was trying to dispatch on the latter (which doesn’t enter the function call).

Also, @jling, in all my other recipes I was able to removed shared_attributes(plot.attributes) and just directly pass in plot.attributes directly. I think in the past it would complain about extraneous arguments if you didn’t do that, but it seems to work fine without that now.