# Odd behaviour of custom labels in Plot recipes

Hello everyone,

I am very new to this forum as this is my first question here. I try to follow the guidelines for posting questions, but please point out any mistakes I do in this regard.

To my question: I want to plot a custom type which has fields `times` (a vector of numbers) and `data`, which is a `Vector` whose elements are either vectors or vectors of vectors. My goal is to have `times` on the x-axis, have a plotline with a custom label for each (nested) element in `data`. However, doing this by using `@recipe` and `@series` somehow mixes up the order of my labels (and I really don’t understand why).
Here is the code of my minimal working example:

``````using Plots

struct MyData
times
data
end

@recipe function f(mydata::MyData)
for (i,d) in enumerate(mydata.data)
@series begin
i==1 && (label --> "Constant")
i==2 && (label --> ["Straight" "Small Random" "Big Random"])
mydata.times, d
end
end
end
times = 1:10
vec = fill(2, 10)
vecofvec = [1:10, rand(10), 10*rand(10)]
mydata = MyData(times, [vec, vecofvec])
plot(mydata)
``````

Which results in this plot

As you can see the order of the labels is mixed up compared to what I want. The first label is still correctly labelling the constant vector `vec`, but the labels for `vecofvec` are set off by one.

I figured out that changing `vec` to a vector of vectors of length two leads to a ‘label offset’ of two and that changing `-->` to `:=` does not help. However, putting the labels in the function call like

``````plot(mydata, label=["Constant" "Straight" "Small Random" "Big Random"])
``````

works (but this is not what I want in the end!).

I would be very grateful if someone could explain to me why this happens and how I can circumvent it.

Hello, please check the code below which seems to work fine.

``````using Plots

struct MyData
times
data
end

@recipe function f(mydata::MyData)
labels = ["Constant" "Straight" "Small Random" "Big Random"]
label --> labels
mydata.times, mydata.data
end

times = 1:10
vc = fill(2, 10)
vecofvec = [1:10, rand(10), 10*rand(10)]
mydata = MyData(times, [vc, vecofvec])
plot(mydata)
``````

Thank you for the very quick answer; your code indeed does work. However, my actual use case is more complicated as the labels are created by external functions and data transformations performed. Additionally I would like to flexibly change the series type of single data entries.
So I think I would have more flexibility with the `@series` construction.

Also for more complicated recipes I would like to understand why my example doesn’t work as expected.

Can you help me with that?

I don’t know why, but it seems that the labels are issued in reverse order compared to the series.

Just changing this line in your recipe to iterate in reverse, seems to fix it:

``````for (i,d) in Iterators.reverse(pairs(mydata.data))
...
``````

Hmm … this seems to work in this case, because the labels of `vecofvec` offset the labels of `vc` by three, but that doesn’t matter since the length of the labels for `vc` is one. So nothing is changed.

In a little more complex example like below the ‘offset’ appears again

``````@recipe function f(mydata::MyData)
for (i,d) in Iterators.reverse(pairs(mydata.data))
@series begin
i==1 && (label --> ["Constant = 2" "Big Random"])
i==2 && (label --> ["Straight" "Small Random" "Constant = 10"])
mydata.times, d
end
end
end
times = 1:10
vecofvec1 = [fill(2, 10), 10*rand(10)]
vecofvec2 = [1:10, rand(10), fill(10, 10)]
mydata = MyData(1:10, [vecofvec1, vecofvec2])
``````

Note that it works if one changes `vecofvec2` to a vector of only two vectors, since the ‘offset’ due to the `vecofvec` labels would ‘iterate’ the labels of `vecofvec2` to its beginning again.

I think you should preprocess the labels first and then call the @series macro. Try this:

``````@recipe function f(mydata::MyData)
labels = String[]
for (i,d) in pairs(mydata.data)
i==1 && push!(labels, "Constant = 2", "Big Random")
i==2 && push!(labels, "Straight", "Small, Random", "Constant = 10")
end
@series begin
label --> permutedims(labels)
mydata.times, mydata.data
end
end
``````
1 Like

Yes, thank you , this works well. Event though I still believe there should be a nicer way to do this. Especially since (what I just figured out) this behaviour does not only affect labels but all `plotattributes` that I want to set in `@series` (so for example `linewidth`). Of course I can preprocess all of them in the way you showed, but there seems to be something I conceptually don’t understand about this macro.

But this is a nice workaround, so thanks!

Just to explain why this happens:
Plots sees 4 series and a 3 element label vector and thus cycles the vector to match the 4 series, so you get `["Straight" "Small Random" "Big Random" "Straight"]` and then it takes the values `2:4` from that vector and that gives you the order you are seeing.

3 Likes

In any case, it seems that simplifying the data structure input to the `@series` macro is helpful here.
Say converting `Vector{Vector{AbstractVector}}` to `Vector{AbstractVector}`

I’d probably write this as

``````using Plots

struct MyData
times
data
end

@recipe function f(mydata::MyData)
@series begin
label := "Constant"
mydata.times, first(mydata.data)
end
for (d, l) in zip(mydata.data,["Straight" "Small Random" "Big Random"])
@series begin
label := l
mydata.times, d
end
end
end
times = 1:10
vec = fill(2, 10)
vecofvec = [1:10, rand(10), 10*rand(10)]
mydata = MyData(times, [vec, vecofvec])
plot(mydata)
``````
1 Like

Thanks for the answer, but I still don’t quite understand. I thought the `@series` macro works by making a kind of local copy of the `plotattributes` and alters them individually for each series. Then it takes the seriesdata provided and basically appends the resulting series to the plot (kind of like `plots!`).
So I believed that for `i==2` I more or less write

``````plot!(times, vecofvec, label = ["Straight" "Small Random" "Big Random"]
``````

Apparently that’s not the case.
So when you say that plots sees 4 series I guess you mean the one series from `vec` and three from `vecofvec` but why does it then not see a 4 element label vector `["Constant" "Straight" "Small Random" "Big Random"]`? I guess that has something to do with the fact that I change the labels during each series?

I think your expectation is reasonable. Its just that you can do stuff in recipes, that you can’t through the `plot/plot!` interface like in this example (you’d need two calls).
So this wasn’t considered up to this point and it might be fixable, but until that you’d need to take one of the other routes.