Trouble understanding how plot recipes dispatches customized types containing other customized types

Can someone explain why the following doesn’t work?

julia> using RecipesBase

julia> struct Pair

julia> struct Pairs

julia> @recipe function plot(pair::Pair)
           pair.x, pair.y

julia> @recipe plot(pairs::Pairs) = pairs.pairs

julia> N = 10

julia> pvec = [Pair(rand(N), rand(N)) for m = 1:15];

julia> pairs = Pairs(pvec);

julia> using Plots

julia> plot(pairs.pairs[1])

Screenshot (1)

julia> plot(pairs)
ERROR: Cannot convert Pair to series data for plotting

Two things: Pair is defined in Base, (:a => 2 is a Pair{Symbol, Int64}). And Type recipes need a type argument as well:

@recipe f(::Type{MyPair}, x::MyPair) = x.x, x.y

Ah, didn’t know that when I built this MWE. Thanks.

Oh that’s right, I did see that they’re kind of inputted twice in the recipe signature, but I’m still confused as to why. I’ve been reading the Recipes documentation over and over, and a lot of it keeps going over my head.


I’m not so sure I did it right.

Yeah, it’s not entirely intuitive. If your recipe signature is (::Type{MyType}, ::MyType) then it’s a type recipe, and will mesh in with everything. Essentially “Hey Plots, if you see a MyType anywhere, do this”. If the signature is (::MyType) that’s a user recipe, and will only work for plot calls with that exact signature. I’m not 100% sure, but I don’t think that recipes in turn invoke user recipes, for instance.

I’m not entirely sure what’s going wrong here, but notice the x axis in your graph. Turns out we’re plotting the x vector against its indices.

I haven’t worked with a Vector{Vector} type before, maybe you actually have to resort to user recipes to get this working. One thing that works is turning it into a Vector{Tuple{Float64, Float64}}: @recipe f(::Type{MyPair}, mp::MyPair) = collect(zip(mp.x, m.y)).

1 Like

Thanks @gustaphe.

This works fine for plot(mypair) where mypair isa MyPair, but plot(mypairs) where mypairs isa MyPairs still doesn’t work.

A couple of questions:

  • Exactly what code are you running?
  • If MyPair is essentially a (x::Vector{Float64}, y::Vector{Float64}) type, then MyPairs is basically a vector of pairs of vectors. What do you expect plotting that to look like?
  • What do you mean by “doesn’t work”?

Having returned to this problem, I cringe at how I expressed my question haha. Thanks for asking.

Basically, suppose I have

struct Path

and I define a plot recipe for it

using RecipesBase
@recipe f(::Type{Path}, p::Path) = collect(zip(p.x, p.y))

which works fine

using Plots
path = Path(1:10, rand(1:10, 10))

But I want to manage a collection of paths. So I define

struct Paths

and instantiate,

paths = Paths([Path(1:10, rand(1:10, 10)) for n = 1:3])

and now need a plot recipe so that plot(paths) “just works,” i.e. it plots all the Paths in the same axes, like so,

which was produced with

for p in paths.xy

which is of course unpreferable.

What doesn’t work is:

@recipe f(::Type{Paths}, p::Paths) = p.xy

which produces: Error: Cannot convert Path to series data for plotting.

I’ve read the documentation here and here over ten times through already, and each time I re-read I learn something new but nothing to answer this question. And no existing discussions online ask nor provide an answer to this question.

Like I mentioned before, this is not intuitive for me either, and I think I’ve written my share of recipes.

plot([[Tuple(rand(2)) for _ in 1:10] for _ in 1:3])

(i.e. plot(::Vector{Vector{Tuple}})) doesn’t work either, so even if your recipe did work properly, it would fail.

I’m not sure if a type recipe is even the right thing here, you will probably never plot a Paths against something else. So maybe a user recipe is enough:

@recipe function f(paths::Paths)
    for path in paths.xy
        @series begin

(maybe needlessly verbose, but I can’t think of a good shorter way)

1 Like

That does seem like the best option. Or better said, the only option that actually produces something, I’ll accept it thanks!