Makie - Example of simple type recipe not working

The docs explain how to write “type recipes”. Can you please provide a MWE with a completely new type? As you can see below, just following the instructions in the docs leads to errors:

import Makie

# custom type
struct Foo end

# type recipe definitions
Makie.plottype(::Foo) = Makie.Scatter
Makie.convert_arguments(::Foo) = (rand(100),)

# test plot
import GLMakie as Mke
Mke.plot(Foo()) # error
ERROR: `Makie.convert_arguments` for the plot type MakieCore.Scatter{Tuple{Foo}} and its conversion trait MakieCore.PointBased() was unsuccessful.

The signature that could not be converted was:
::Foo

Makie needs to convert all plot input arguments to types that can be consumed by the backends (typically Arrays with Float32 elements).
You can define a method for `Makie.convert_arguments` (a type recipe) for these types or their supertypes to make this set of arguments convertible (See http://docs.makie.org/stable/documentation/recipes/index.html).

Alternatively, you can define `Makie.convert_single_argument` for single arguments which have types that are unknown to Makie but which can be converted to known types and fed back to the conversion pipeline.

Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:35
  [2] convert_arguments(T::Type{MakieCore.Scatter{Tuple{Foo}}}, args::Foo; kw::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/conversions.jl:17
  [3] convert_arguments(T::Type{MakieCore.Scatter{Tuple{Foo}}}, args::Foo)
    @ Makie ~/.julia/packages/Makie/uAmck/src/conversions.jl:7
  [4] plot!(scene::Makie.Scene, P::Type{Any}, attributes::MakieCore.Attributes, args::Foo; kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:292
  [5] plot!(scene::Makie.Scene, P::Type{Any}, attributes::MakieCore.Attributes, args::Foo)
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:275
  [6] plot(P::Type{Any}, args::Foo; axis::NamedTuple{(), Tuple{}}, figure::NamedTuple{(), Tuple{}}, kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/figureplotting.jl:48
  [7] plot(P::Type{Any}, args::Foo)
    @ Makie ~/.julia/packages/Makie/uAmck/src/figureplotting.jl:31
  [8] plot(args::Foo; attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ MakieCore ~/.julia/packages/MakieCore/bttjb/src/recipes.jl:34
  [9] plot(args::Foo)
    @ MakieCore ~/.julia/packages/MakieCore/bttjb/src/recipes.jl:33
 [10] top-level scope
    @ REPL[9]:1

caused by: MethodError: no method matching convert_arguments(::Type{MakieCore.Scatter{Tuple{Foo}}}, ::Foo)

Closest candidates are:
  convert_arguments(::Union{Type{Any}, Type{<:MakieCore.AbstractPlot}}, ::Any...; kw...)
   @ Makie ~/.julia/packages/Makie/uAmck/src/conversions.jl:7
  convert_arguments(::Union{Type{Any}, Type{<:MakieCore.AbstractPlot}}, ::Distributions.Distribution)
   @ Makie ~/.julia/packages/Makie/uAmck/src/stats/distributions.jl:19
  convert_arguments(::Union{Type{Any}, Type{<:MakieCore.AbstractPlot}}, ::IntervalSets.AbstractInterval, ::StatsBase.ECDF)
   @ Makie ~/.julia/packages/Makie/uAmck/src/stats/ecdf.jl:27
  ...

Stacktrace:
  [1] convert_arguments_individually(T::Type{MakieCore.Scatter{Tuple{Foo}}}, args::Foo)
    @ Makie ~/.julia/packages/Makie/uAmck/src/conversions.jl:47
  [2] convert_arguments(T::Type{MakieCore.Scatter{Tuple{Foo}}}, args::Foo; kw::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/conversions.jl:14
  [3] convert_arguments(T::Type{MakieCore.Scatter{Tuple{Foo}}}, args::Foo)
    @ Makie ~/.julia/packages/Makie/uAmck/src/conversions.jl:7

Notice that I can already define what the documentation calls “full recipes”, but I am now interested in simple “type recipes” to forward types to existing recipes.

You need to define the plot type for which you define the convert_arguments:

Makie.convert_arguments(S::Type{<:Scatter}, ::Foo) = convert_arguments(S, rand(10))
1 Like

Hi @sdanisch the solution doesn’t seem to work with arbitrary plot types:

MWE:

using GeoStats

# here is a custom histogram type
d = georef((z=rand(100),), rand(2,100))
h = EmpiricalHistogram(d, :z)

import Makie

# define recipe for EmpiricalHistogram
Makie.convert_arguments(P::Type{<:Makie.Hist}, h::EmpiricalHistogram) =
  Makie.convert_arguments(P, (h.hist,))

import GLMakie as Mke

# test recipe with GLMakie
Mke.plot(h)
ERROR: PlotMethodError: no plot method for arguments (::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}). To support these arguments, define
  plot!(::MakieCore.Combined{Any, S} where S<:Tuple{EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}})
Available methods are:

Stacktrace:
  [1] _plot!(p::MakieCore.Combined{Any, Tuple{EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:321
  [2] plot!(p::MakieCore.Combined{Any, Tuple{EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:316
  [3] plot!(scene::Makie.Scene, P::Type{MakieCore.Combined{Any, Tuple{EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}}}, attributes::MakieCore.Attributes, input::Tuple{Observables.Observable{EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}}, args::Observables.Observable{Tuple{EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:398
  [4] plot!(scene::Makie.Scene, P::Type{Any}, attributes::MakieCore.Attributes, args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}; kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:310
  [5] plot!(scene::Makie.Scene, P::Type{Any}, attributes::MakieCore.Attributes, args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:275
  [6] plot(P::Type{Any}, args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}; axis::NamedTuple{(), Tuple{}}, figure::NamedTuple{(), Tuple{}}, kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/figureplotting.jl:48
  [7] plot(P::Type{Any}, args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/figureplotting.jl:31
  [8] plot(args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}; attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ MakieCore ~/.julia/packages/MakieCore/bttjb/src/recipes.jl:34
  [9] plot(args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}})
    @ MakieCore ~/.julia/packages/MakieCore/bttjb/src/recipes.jl:33
 [10] top-level scope
    @ REPL[12]:1

I then tried adding the method for Makie.plottype:

Makie.plottype(::EmpiricalHistogram) = Makie.Hist

but a new error is thrown:

ERROR: MethodError: no method matching -(::StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}, ::Float64)

Closest candidates are:
  -(::T, ::T) where T<:Union{Float16, Float32, Float64}
   @ Base float.jl:409
  -(::CoDa.HMatrix, ::Any)
   @ CoDa ~/.julia/dev/CoDa/src/matrices.jl:120
  -(::Animations.Keyframe, ::Real)
   @ Animations ~/.julia/packages/Animations/bSVbm/src/keyframes.jl:9
  ...

Stacktrace:
  [1] pick_hist_edges(vals::Tuple{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}, bins::Int64)
    @ Makie ~/.julia/packages/Makie/uAmck/src/stats/hist.jl:147
  [2] #map#136
    @ ~/.julia/packages/Makie/uAmck/src/scenes.jl:185 [inlined]
  [3] map(f::typeof(Makie.pick_hist_edges), scene::MakieCore.Combined{Makie.hist, Tuple{Tuple{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}}, arg1::Observables.Observable{Tuple{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}, args::Observables.Observable{Any})
    @ Makie ~/.julia/packages/Makie/uAmck/src/scenes.jl:182
  [4] plot!(plot::MakieCore.Combined{Makie.hist, Tuple{Tuple{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/stats/hist.jl:163
  [5] plot!(scene::Makie.Scene, P::Type{MakieCore.Combined{Makie.hist, Tuple{EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}}}, attributes::MakieCore.Attributes, input::Tuple{Observables.Observable{EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}}, args::Observables.Observable{Tuple{Tuple{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:398
  [6] plot!(scene::Makie.Scene, P::Type{Any}, attributes::MakieCore.Attributes, args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}; kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/interfaces.jl:310
  [7] plot!
    @ ~/.julia/packages/Makie/uAmck/src/interfaces.jl:275 [inlined]
  [8] plot(P::Type{Any}, args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}}; axis::NamedTuple{(), Tuple{}}, figure::NamedTuple{(), Tuple{}}, kw_attributes::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Makie ~/.julia/packages/Makie/uAmck/src/figureplotting.jl:48
  [9] plot
    @ ~/.julia/packages/Makie/uAmck/src/figureplotting.jl:31 [inlined]
 [10] #plot#12
    @ ~/.julia/packages/MakieCore/bttjb/src/recipes.jl:34 [inlined]
 [11] plot(args::EmpiricalHistogram{StatsBase.Histogram{Float64, 1, Tuple{StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}}}})
    @ MakieCore ~/.julia/packages/MakieCore/bttjb/src/recipes.jl:33
 [12] top-level scope
    @ REPL[12]:1

Just do Makie.convert_arguments(P, h.hist)

Will give it a try. Can we also forward kwargs?

Nevermind, the kwargs are already forwarded :+1:

Actually, the kwargs are fowarded in the call site, but how to forward the kwargs in the convert_arguments definition? Is it possible?

Say I have a full recipe MyPlot with kwarg myarg, i.e. I can call it with:

myplot(obj, myarg=1)

Now I want to define the same recipe for another type. I need to retrieve the value of myarg from this new type and forward it to the kwarg of the MyPlot recipe:

Makie.plottype(::Foo) = MyPlot
Makie.convert_arguments(P::Type{<:MyPlot}, f::Foo) =
  Makie.convert_arguments(P, f.x; myarg=f.a)

I tried the above, but it didn’t work.

I believe you need to use PlotSpec, which can be returned from convert_arguments, to forward keyword arguments.

Mind sharing an example @DanielVandH ?

Here’s an example where the type passes the kwarg markersize:

using CairoMakie
struct SomeMarkers 
    x::Vector{Float64}
    y::Vector{Float64}
    size::Vector{Int}
end
Makie.plottype(::SomeMarkers) = Makie.Scatter
function Makie.convert_arguments(S::Type{<:Scatter}, sm::SomeMarkers) 
    xy = Makie.convert_arguments(S, sm.x, sm.y)
    return PlotSpec{Scatter}(xy...; markersize = sm.size)
end
x = rand(100)
y = rand(100)
sizes = rand(1:10, 100)
sm = SomeMarkers(x, y, sizes)

scatter(sm)

image

1 Like

That is perfect @DanielVandH :slight_smile:

1 Like