Redirect a plot type in Makie while keeping color consistent

I have a histogram type that I’d like to customize its plotting for a few type of plots: Scatter, BarPlot, Hist, StepHist etc.

For some of them, this is very straight forward:

julia> using CairoMakie

julia> struct MyHist
           bincounts
           binedges
       end

julia> h = MyHist([1, 10, 100], 0:3)

julia> Makie.convert_arguments(P::Type{<:Stairs}, h::MyHist) = convert_arguments(P, h.binedges, [0; h.bincounts])

julia> stairs(h)

However, for plot type such as StepHist, I failed to properly “override” the plot, what I tried is to define:

function Makie.plot!(input::StepHist{<:Tuple{<:MyHist}})
    h = input[1][]
    Makie.stairs!(input, h)
    input
end

this works in the naive case, which would produce exactly the same plot as above:

julia> h = MyHist([1, 10, 100], 0:3)

julia> stephist(h)

However, it fails when we use it together with other stuff:

stephist(h; color=:red)

So initially I tried to fix this by changing the definition of plot!() to:

function Makie.plot!(input::StepHist{<:Tuple{<:MyHist}})
    h = input[1][]
    C = input[:color][]
    Makie.stairs!(input, h; color=C)
    input
end

now this works:

stephist(h; color=:red)

but there are edge cases like:

julia> begin
           stephist(h);
           h2 = MyHist([5, 20, 120], 0:3)
           stairs!(h2)
           current_figure()
       end

Notice one is stephist the other is stairs!, that produce:

Notice, if we delete the color = C) part from the plot!() function above, the latest code plots the colors as expected:

First you might try passing the attributes directly and not decomposing the observable, i.e., removing the [].

If you need to modify them you can do it through lift.

Pass all the attributes to stairs but delete the ones which don’t belong. We ought to have a merge function for this but you can do something like the following:

# plot = input plot
scene = Makie.parent_scene(plot)
stephist_default = Makie.default_theme(scene, Makie.StepHist)
for key in keys(stephist_default)
stephist_default[key] = get(plot.attributes, key, stephist_default[key])
end
stephist!(scene, input, attributes)
end

what’s the relationship between the plot in your definition and the input (I assume your input is the same as my input)?

ah sorry in this case plot is just the argument passed to the function, my phrasing was probably a bit off

no worries, I still can’t quite get it to work:

function Makie.plot!(plot::StepHist{<:Tuple{<:MyHist}})
    scene = Makie.parent_scene(plot)
    attributes = Makie.default_theme(scene, Makie.Stairs)
    for key in keys(stephist_default)
        attributes[key] = get(plot.attributes, key, attributes[key])
    end
    stairs!(scene, plot, attributes)
    plot
end

notice I’m eventually calling stairs!(), I think you had a typo in your example, I also fixed the input on the last line which wasn’t define in your first snippet

@asinghvi17 specifically, I think the defined convert rule is not used correctly in this code path.

I get:

julia> Makie.convert_arguments(P::Type{<:Stairs}, h::MyHist) = convert_arguments(P, h.binedges, [0; h.bincounts])

julia> begin
           stephist(h);
           h2 = MyHist([5, 20, 120], 0:3)
           stairs!(h2)
           current_figure()
       end
ERROR: `Makie.convert_arguments` for the plot type Plot{Makie.stairs} and its conversion trait PointBased() was unsuccessful.

The signature that could not be converted was:
::Plot{Makie.stephist, Tuple{MyHist}}, ::Attributes

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(::Type{Plot{Makie.stairs}}, ::Plot{Makie.stephist, Tuple{MyHist}}, ::Vararg{Any}; kw::@Kwargs{})
    @ Makie ~/Documents/github/Makie.jl/src/conversions.jl:17
  [3] convert_arguments(::Type{Plot{Makie.stairs}}, ::Plot{Makie.stephist, Tuple{MyHist}}, ::Attributes)
    @ Makie ~/Documents/github/Makie.jl/src/conversions.jl:7
  [4] (Plot{Makie.stairs})(args::Tuple{Plot{Makie.stephist, Tuple{MyHist}}, Attributes}, plot_attributes::Dict{Symbol, Any})
    @ Makie ~/Documents/github/Makie.jl/src/interfaces.jl:139
  [5] _create_plot!(::Function, ::Dict{Symbol, Any}, ::Scene, ::Plot{Makie.stephist, Tuple{MyHist}}, ::Vararg{Any})
    @ Makie ~/Documents/github/Makie.jl/src/figureplotting.jl:289
stairs!(plot, input, attributes)

is probably what you want here, where input is whatever you’ve processed from plot.converted.

Alternatively you can return a PlotSpec from the convert method if you don’t need to set anything complex up…

sorry I don’t understand what does this mean, I tried the following:

julia> struct MyHist
           bincounts
           binedges
       end

julia> h = MyHist([1, 10, 100], 0:3)
MyHist([1, 10, 100], 0:3)

julia> Makie.convert_arguments(P::Type{<:Stairs}, h::MyHist) = convert_arguments(P, h.binedges, [0; h.bincounts])

julia> function Makie.plot!(plot::StepHist{<:Tuple{<:MyHist}})
           scene = Makie.parent_scene(plot)
           attributes = Makie.default_theme(scene, Makie.Stairs)
           for key in keys(attributes)
               attributes[key] = get(plot.attributes, key, attributes[key])
           end
           stairs!(plot, plot.converted, attributes)
           plot
       end

julia> begin
           stephist(h);
           h2 = MyHist([5, 20, 120], 0:3)
           stairs!(h2)
           current_figure()
       end
ERROR: `Makie.convert_arguments` for the plot type Plot{Makie.stairs} and its conversion trait PointBased() was unsuccessful.

The signature that could not be converted was:
::Tuple{Observable{MyHist}}, ::Attributes

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.

ok I think attributes needs to be the second argument:

julia> function Makie.plot!(plot::StepHist{<:Tuple{<:MyHist}})
           scene = Makie.parent_scene(plot)
           attributes = Makie.default_theme(scene, Makie.Stairs)
           for key in keys(attributes)
               attributes[key] = get(plot.attributes, key, attributes[key])
           end
           stairs!(plot, attributes, plot[1])
           plot
       end