Plots: Only one series as primary plot

I frequently make figures that look like this

m = randn(100)
n = m .+ randn(100,50)
plot(m, lab="mean", l=(5,))
plot!(n; lab="realizations", l=(:black, 0.1))

afig
Here, I would prefer to only have one of the “realizations” included in the legend. I created a small struct to solve this, so that the figure looks like this

plot(m, lab="mean", l=(5,))
plot!(n; lab="realizations", l=(:black, 0.1), primary=onlyone(true))

afig2
The definition of the struct onlyone is below. Is this a common enough problem that people want something like this included in Plots? Either this approach or a keyword argument etc.?

struct onlyone <: AbstractMatrix{Bool}
    v::Bool
end
function Base.iterate(o::onlyone, state=1)
      state == 1 ? o.v : !o.v, state+1
end
Base.size(o::onlyone) = (1,typemax(Int))
Base.length(o::onlyone) = typemax(Int)
Base.getindex(o::onlyone,i) = i == 1 ? o.v : !o.v
Base.getindex(o::onlyone,i,j) = j == 1 ? o.v : !o.v

Could you not just plot n[1] with Label, and then n[2:end] with the same colour and no labels? It’s one extra call so not the prettiest thing ever but seems simpler and more portable?

Yeah I’ve frequently done something like that, but I wanted to avoid that as it makes my plotting code close to twice as verbose and much harder to overlook.

…that’s what I do (plot!(n[:,1],label="realizations",...), etc.). Another alternative:

LAB = hcat("realizations",fill("",1,49))
plot(m,label="mean",lw=5)
plot!(n,label=LAB,lc=:black,la=0.1)

…which saves a plot!() statement, but adds a variable.

Often, I find the result more (aesthetically) pleasing if add the primary plot lastly — so that it is not hidden under a cloud of realizations. To ensure that the primary plot legend appears first in the label list, that can – of course – be done by replotting the primary plot without label.

I can understand your point when you make lots of plotting. I probably make simpler plots…
To me, the two things I miss the most in Plots are:

  1. The possibility to specify the number of columns in the label plate (matplotlib has good support for this), and
  2. The possibility to add a plot title (i.e., a main title of the plot window in the case of more than one column in the plot layout).

That’s a nice little trick. My only concern is about whether people would realise this was in there.

Yeah Plots certainly doesn’t make it impossible for me, it’s just that all available alternatives are awkward and verbose :confused:

How does anyone realize any feature of a package? It’s mentioned in the documentation! E.g., if it was a keyword firstprimary=true, it would be listed under “attributes” in the documentation.

Still, I’m not sure if the approach I implemented is the best. It covers what I usually want to do, but perhaps there might be cases where one want’s to apply other settings to the first of a number of series. My version only supports Bool. Maybe one want’s to apply similar logic to non-booleans, even though I couldn’t immediately think of a use case.

I think the inherent logic in Plots to do this would be to keep push!ing to the same series, with NaNs separating individual line segments. In this case that would involve putting everything into one vector instead of as now hcatting them. I can see how that might be awkward.

Maybe I’m not thinking things through, but is there any logic in specifying several series and a single label?

  • Could one turn the whole thing around and say that if a single label is provided in a plot command, plot()/plot!() should only add a single label to the legend plate?
  • On the other hand, if a row matrix of labels is provided, the standard behavior of cycling through the label list should be used?

If there are any exceptions to this, the user could make a for loop to get around the limitation.

This way, one can avoid new keywords, and get the behavior that is expected/wanted in (almost) all cases?

1 Like

To me the preferred interface would be to be able to say uniquelabels=true, or something like that, to avoid showing identically-looking labels.

2 Likes

Would make sense

I got curious now, is this considered part of the API of Plots, i.e., is the user encouraged to manipulate the series_list manually? Or how do you push to a series?

Edit: I managed to answer my own question:
http://docs.juliaplots.org/latest/examples/gr/#functions-adding-data-and-animations
I didn’t know this interface existed, neat!

Edit2: After having tried it a bit, I’m not sure if the documented interface is working

p = plot(randn(10,2))

push!(p, [1,2]) # Working
x,y = p[1]      # Not working
x,y = p[1][1]   # Not working
p[1][1] = (1:10, randn(10)) # Not working
p[1] = (1:10, 1:10)         # Working

Hmm, where is that interface documented?
Normally you’re not expected to mess with that - but pushing to a series should be as easy as push!(plt, 1, newelement)

To get x I would normally p[1][1][:x], i.e. “get x from the first series in the first subplot of the plot p”

Here http://docs.juliaplots.org/latest/examples/gr/#functions-adding-data-and-animations

Get series data: x, y = plt[i] . Set series data: plt[i] = (x,y) . Add to the series with push! / append! .

hahaha, so yeah that doesn’t work. The closest would be x, y = Plots.getxy(p, 1)

It seems that at least something along those lines is implemented as the above works. It doesn’t really matter if the interface uses getindex/setindex!, but some supported interface to push/pull data from a plot would be very useful.