Creating a plot containing subplots called from a function

Question about plotting, plot handles, subplots, etc. I’m trying to make a plot function that accepts a vector of a custom data structure. For each entry in the vector, another plot function is called which plots an individual element of the custom type. This function arrangement must be preserved since I need to have a separate individual element version of the function.

I’ve created a simplified version of this arrangement shown below, which does not currently work as expected, and only produces an empty plot. If I run ‘display(current())’, a plot of the last vector element shows up. Any help would be greatly appreciated. Thanks in advance.

using Plots

# Create a custom data struct
Base.@kwdef mutable struct CustType
    x::Vector{Float64}  =   1:10
    y::Vector{Float64} = randn(10)
    z::Vector{Float64} = randn(10)
end

# Plot a single element of CustType in two subplots
function Plots.plot!(v::CustType; kwargs...)
    sub1 = Plots.plot(v.x,v.y)
    sub2 = Plots.plot(v.x,v.z)
    ph = Plots.plot!(sub1,sub2)
    return ph
end

# Create a plot consisting of two subplots from a vector of CustType
function Plots.plot(nt::Vector{CustType};kwargs...)
    plotly()
    ph = Plots.plot()
    for v in nt 
        Plots.plot!(v)
    end
    return ph
end

# Test the above code ...
ct_vec = fill(CustType(),5)
Plots.plot(ct_vec)

First of all, it would be better not to use Plots.plot to define your own methods, but instead, use a new name or plot recipes to define your custom plots.

I don’t use Plots.jl a lot these days, but it seems that you should combine plots with the Plots.plot(..., layout=...) as described here Tutorial · Plots.

In your current version, it is unclear how to add the sub1, sub2 to the existing plot… I think what happens is that it adds sub1, sub2 to the last current plot, which is in this setting sub2 (as that was the last time the Plots.plot(...) command was called.

To solve the general problem, you implementation could maybe be done like this:

plot_xyz(v) = plot(v.x, [v.y v.z], layout = (1,2) )

ct_vec = fill(CustType(), 5)
plts = plot_xyz.(ct_vec)
plot(plts..., layout = (length(ct_vec, 1) )

Even better is maybe to deal with the layout of all plots in the end, such that the scales are better:

plot_xyz(v) = plot(v.x, v.y), plot(v.x, v.z)
plts = plot_xyz.(ct_vec)

plot(stack(plts)..., layout = (5,2) )

Thanks for the help. Unfortunately, I have to keep the subplots, layout, etc. in the lower-level plot function so that it can stand on its own to generate a plot of a single element of CustType in a different part of the code.

It’s hard to see why this wouldn’t be possible with the correct handing of plot handles, etc. Maybe a different plotting package or backend might be better?

In that case, you could do it as in the first example. E.g., produce one plot for the custom datatype and then combine all of them by putting them together into a column.

I think it is possible, but you must provide the layout information to the plot functions, as the plotting package cannot guess how you want to arrange the plots in a layout (e.g. do you want rows, columns etc…).

Makie.jl has a very modern layout system (probably one of the best). But what you want to archive is also doable in Plots.jl.

So in regards to the layout, that is all set up in the lower level plot function. The actual function in my code is a little more complicated, and does indeed define the layout pattern. It is as follows:

ph = Plots.plot!(p1...,p2...,p3...,p4..., p5..., layout=(5,n),size=(1000,800))

p1 … p5 are plot handles similar to the simplified sub1, sub2 in my simplified example. As I said, this lower level plot function works well when called directly in other parts of our code. I just need to be able to call if from the higher level plot function when I have a vector of data I wish to plot.

Your suggestion above about combining the plot call into one single call:

plot_xyz(v) = plot(v.x, [v.y v.z], layout = (1,2) )

runs, but does not produce subplots. Instead, I get a single plot with two lines and 10 labels (y1…y10) shown in the legend.

Thanks for your help!

So after more experimentation, I found the following code to work:

using Plots

# Create a custom data struct
Base.@kwdef mutable struct CustType
    x::Vector{Float64}  =   1:10
    y::Vector{Float64} = randn(10)
    z::Vector{Float64} = randn(10)
end

# Plot a single element of CustType in two subplots
function Plots.plot!(v::CustType; kwargs...)
    ph = Plots.current() 
    if ph.n == 0
        ph = Plots.plot(layout = (1,2))
    end
    Plots.plot!(ph.subplots[1],v.x,v.y; merge(kwargs,Dict(:label=>false))...)
    Plots.plot!(ph.subplots[2],v.x,v.z; kwargs...)
    return ph
end

# Create a plot consisting of two subplots from a vector of CustType
function Plots.plot(nt::Vector{CustType};kwargs...)
    plotly()
    Plots.plot()
    for (i,v) in enumerate(nt) 
        Plots.plot!(v, label="Run $(i)")
    end
    return current()
end

# Test the above code ...
ct_vec = [CustType() for _ in 1:5]
ph = Plots.plot(ct_vec)
display(ph)

If there’s a better way to get this to fit within the overall structure, I’d love to know. Thanks for the help!