Modifying Plots.jl subplots in-place

As discussed recently in this thread, we are wondering whether it is possible to create one initial plot with subplots, which then can be individually modified by push!.

So instead of creating two separate plots and then combining them in a call to plot:

using Plots

N = 200

u1 = 20*rand(3, N)
u2 = 15*rand(3, N)

plt1 = plot3d(
    1,
    xlim = (-30, 30),
    ylim = (-30, 30),
    zlim = (0, 60),
    title = ["Some trajectory"],
    legend = false,
    marker = 2,
)

plt2 = plot3d(
    1,
    xlim = (-30, 30),
    ylim = (-30, 30),
    zlim = (0, 60),
    title = ["Other trajectory"],
    legend = false,
    marker = 2,
)

@gif for i in range(1, size(u1, 2))
    push!(plt1, u1[:, i]...)
    push!(plt2, u2[:, i]...)
    plot(plt1, plt2)
end every 10

I’d expect something like

N = 200

u1 = 20*rand(3, N)
u2 = 15*rand(3, N)


plt = plot3d(
    1,
    xlim = (-30, 30),
    ylim = (-30, 30),
    zlim = (0, 60),
    title = ["Some trajectory" "Other trajectory"],
    legend = false,
    marker = 2,
    layout = (1, 2)
)

@gif for i in range(1, size(u1, 2))
    push!(plt[1], u1[:, i]...)
    push!(plt[2], u2[:, i]...)
end every 10

or even being able to push to both subplots in one joint call.

1 Like

Is this animation what you are expecting?

using Plots
n=150
t =range(0, 4pi, n)
x=sin.(t)
y=t
z=cos.(t)
l = @layout [a b] 
p = plot(layout = l, xlim = (-1.2, 1.2), ylim = (-0.2, 4pi+0.2),
        zlim = (-1.2, 1.2), legend = false);

@gif for i in 1:5:n
    plot!(p[1], x[1:i], y[1:i], z[1:i])
    plot!(p[2], x[1:i], y[1:i], z[1:i])
end every 3

I changed your data because it wasn’t quite visible how it works.

1 Like

I think this is how it’s supposed to look like in the end (the data in the original post just plots random points in succession to show the idea).

What isn’t quite clear to me is whether updating the plot using push! (as described in this example of the docs: Home · Plots ) or using plot! (as you showed) should be preferred when animating a plot. Is one faster than the other?

And at least in the example from the original post, using the push! method with subplots doesn’t seem to work. Perhaps we were doing it wrong…

It doesn’t work with push!. Just replace plot! by push!, and see the error message, which tells, as I remember ( I’m writing from my phone) you cannot push to subplots.

That’s what I said :sweat_smile:
The error I get is just a generic MethodError, hence the question if this is just a “missing feature” or if there is more behind it.

Stacktrace
julia> @gif for i in 1:5:n
           push!(p[1], x[1:i], y[1:i], z[1:i])
           push!(p[2], x[1:i], y[1:i], z[1:i])
       end every 3
ERROR: MethodError: no method matching push!(::Plots.Subplot{Plots.GRBackend}, ::Vector{Float64})

Closest candidates are:
  push!(::Any, ::Any, ::Any)
   @ Base abstractarray.jl:3389
  push!(::Any, ::Any, ::Any, ::Any...)
   @ Base abstractarray.jl:3390
  push!(::DataStructures.DefaultDict, ::Any)
   @ DataStructures ~/.julia/packages/DataStructures/MKv4P/src/default_dict.jl:139
  ...

Stacktrace:
 [1] push!(A::Plots.Subplot{Plots.GRBackend}, a::Vector{Float64}, b::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64})
   @ Base ./abstractarray.jl:3389
 [2] push!(A::Plots.Subplot{Plots.GRBackend}, a::Vector{Float64}, b::StepRangeLen{Float64, Base.TwicePrecision{Float64}, Base.TwicePrecision{Float64}, Int64}, c::Vector{Float64})
   @ Base ./abstractarray.jl:3390
 [3] macro expansion
   @ ./REPL[52]:2 [inlined]
 [4] top-level scope
   @ ~/.julia/packages/Plots/sxUvK/src/animation.jl:251

I couldn’t find out if there is a technical reason why push!ing new data to subplots doesn’t work, but push!ing to a regular plot works. From your answer I understand that there might be one. Do you know if this is documented somewhere?

The example right below the Lorenz attractor also uses subplots, but creates a new plot every iteration. Seems like this (or the approach by @empet ) is the only supported way at the moment?

When the two subplots have distinct axes limits, then the above code is slightly modified, as follows:

using Plots
n=150
t =range(0, 4pi, n)
x=sin.(t)
y=t
z=cos.(t)
l = @layout [a b] 
p = plot(layout = l)
@gif for i in 1:5:n
  plot!(p[1], x[1:i], y[1:i], z[1:i], xlim = (-1.2, 1.2), ylim = (-0.2, 4pi+0.2),
        zlim = (-1.2, 1.2), legend = false)
  plot!(p[2], z[1:i], x[1:i], y[1:i], xlim = (-1.2, 1.2), ylim = (-1.2, 1.2),
        zlim=(-0.2, 4pi+0.2), legend=false)
end every 3
1 Like