Makie @recipe need understanding

I’m having no luck finding a working example of how to define a makie @recipe for a custom type, or in this case custom types.

I have a home grown quaternion and a 3D vector type which match better how legacy simulations function making conversion better. The Vector type presents an x, y, and z field. The quaternion can rotate those vectors.

In Plots.jl recipe it is fairly easy to specify a plot overload that throws up 3 lines representing the XYZ axis of a quaternion rotation into a plot where I have an optional origin for the axis and a scale factor for the length of the axis.

The Plot.jl recipe I’m trying to emulate starts out with a from like this.

quaternion_plot
@recipe function f(q::quaternion, v::xyzVecT = xyzVecT(); scalefactor = 1.0)
# Quaternion will plot the X, Y and Z axis at the location
    seriestype := :path
    linestyle --> :solid
    arrow --> :True
    seriesalpha --> 0.4
    marker --> :none
    xaxis = xVec(scalefactor) * q
    yaxis = yVec(scalefactor) * q
    zaxis = zVec(scalefactor) * q
    quickFmt(a,b) = [a.x,b.x], [a.y, b.y], [a.z, b.z]
    # Plot the X Axis in Blue with reduced alpha
    @series begin
        label --> :none
        seriescolor --> :blue
        marker := :none
        quickFmt(v, v + xaxis)  # Remove the vector markers
    end
    @series begin
        label --> :none
        seriescolor --> :green
        marker := :none
        quickFmt(v, v + yaxis) # Remove the vector markers
    end
    @series begin
        label --> :none
        seriescolor --> :red
        marker := :none
        quickFmt(v, v + zaxis) # Remove the vector markers
    end

    # Direction Pointers
    @series begin
        label --> "X"
        seriestype --> :scatter
        seriescolor --> :blue
        marker := :diamond
        temp=v + xaxis
        [temp.x], [temp.y], [temp.z]
    end

    @series begin
        label --> "Y"
        seriestype --> :scatter
        seriescolor --> :green
        marker := :circle
        temp=v + yaxis
        [temp.x], [temp.y], [temp.z]
    end

    @series begin
        label --> "Z"
        seriestype --> :scatter
        seriescolor --> :red
        marker := :square
        temp=v + zaxis
        [temp.x], [temp.y], [temp.z]
    end

end

I cannot figure out how to get the Makie.@recipe to do anything, even if I skip the origin shift and the scale factor and just take the quaternion. I find just the one documentation example and a dataframe example on the internet. Can someone help me understand this @recipe concept and figure out how to use it or is it just too far off and I need to forget a recipe.

@recipe(QPlot, q, origin, scale_factor ) do scene
        Attributes(
            color = :red,
            style = :dashed
        )
    end

function plot!(qp::QPlot)
        xaxis = xVec(qp[:scale_factor][]) * qp[:q][]
        yaxis = yVec(qp[:scale_factor][]) * qp[:q][]
        zaxis = zVec(qp[:scale_factor][]) * qp[:q][]
        quickFmt(a,b) = [a.x,b.x], [a.y, b.y], [a.z, b.z]
        lines!(qp,quickFmt(qp[:origin], qp[:origin] + xaxis),color=qp[:color][],linestyle=qp[:style][])
        lines!(qp,quickFmt(qp[:origin], qp[:origin] + yaxis),color=qp[:color][],linestyle=qp[:style][])
        lines!(qp,quickFmt(qp[:origin], qp[:origin] + zaxis),color=qp[:color][],linestyle=qp[:style][])
        qp
    end

Note: I’m assuming the quickFmt works the same for lines! as it did for the plot recipe, but I haven’t even gotten into the function to debug any of that stuff.

Thanks for any help and understanding.
Best Regards,
Allan

It needs to be Makie.plot! if you want to extend that function, that might be the first thing to try? There are a couple things that could be improved, for example you unwrap all observables so the plot will not be interactively updateable. Might have time to write an example later.

yeah, forget the observable unwrapping. I was following the DataFrame example I found, but that makes sense.

Try this to get started:

julia> @recipe(QPlot, q, origin, scale_factor ) do scene
           Attributes(
               color = :red,
               style = :dashed
           )
       end

julia> function Makie.plot!(p::QPlot)
           axs = lift(p[:q], p[:scale_factor], p[:origin]) do q, scale, ori
               segments = mapreduce(vcat, [Vec3f(1, 0, 0), Vec3f(0, 1, 0), Vec3f(0, 0, 1)]) do v
                   v_rot = q * (scale * v)
                   [ori, ori + v_rot]
               end
           end
           linesegments!(p, axs, color = [:red, :green, :blue])
           p
       end

julia> qplot(Makie.to_rotation(1.0), Point3f(2, 2, 2), 3.0)

Whoa. That’s fancy. Thank you! I’ll give it a shot!

I think I have this part working now… in a package extension no left.
Do you by any change know, how to have default arguments for origin and scale_factor in this recipe or pass in kw arguments?

I would turn them into attributes if you need defaults and keywords.

1 Like

I think I have it all working, thank you.
I am having trouble when placing this recipe in an extension that gets loaded on MakieCore.

I don’t seem to be able to make the recipe generated plot function (quaternionplot) “external” or link back to the original module. Usually I would do something like OriginalModule.quaternionplot. But since that quaternionplot is created by the recipe, I can’t seem to make it hook up by just putting “external quaternionplot” in the extension.

I did have it working by defining empty functions in the OriginalModule for quaternionplot and quaternionplot!, but then I get a warning about broken modules because the @recipe macro was redefining it. ** incremental compilation may be fatally broken for this module **

I don’t see how it can have it both ways. I haven’t yet seen an example that lets me understand the best practice here. Any ideas?

Best Regards,
Allan Baker

Inside OriginalModule:

"quaternionplot requires a Makie backend to work as it is overloaded in OriginalModuleMakieExt.jl."
function quaternionplot() end

"quaternionplot! requires a Makie backend to work as it is overloaded in OriginalModuleMakieExt.jl."
function quaternionplot!() end

export quaternionplot, quaternionplot!

Inside extension with recipe.
import OriginalModule: quaternionplot, quaternionplot!

Warning looks something like this where the I have changed some of the names to keep it generic:

 WARNING: Method definition quaternionplot() in module OriginalModule at D:\julia\development\OriginalModule\src\OriginalModule.jl:445 overwritten in module OriginalModuleMakieExt at C:\Users\username\.julia\packages\MakieCore\bttjb\src\recipes.jl:173.
│    ** incremental compilation may be fatally broken for this module **

But it works. It just has nasty warnings.

If you put

function quaternionplot end  # <-- no parentheses

instead, does it work without warning?

The latter defines an empty function (a function with no methods), whereas the former defines a function with one method (which does nothing).