PlotlyJS.jl question

What if I am trying to animate the evolution of a surface after the execution of some command that modifies the z component of the surface? I have written the following but the trace does not update when you run the code :


using PlotlyJS

mutable struct Node 

    #=

    =#

    x_::Real
    y_::Real
    potential::Real
    edge_flag::Bool ## if 1 then the node is on an edge
    # adjacency_list::Array{Real,1}

end 

mutable struct Lattice

     #=

    =#

    length::Integer
    n_grids::Integer 
    lattice_grid::Array{Node}
end 

# number-> √number* √number==number ? true : return false 

function checkperfsquare(number::Integer)

     #=

    =#

    if √number* √number==number 
        return true 
    else 
        return false
    end 
end 

function checkifedge(x,y,L)

    #=

    =#

    if (x>=0 || x<=L) && (y>=0 && y<=L)
        if (x==L || x==0 || y==L || y==0) || (x>L/2 && y<L/2)
            return true 
        else 
            return false 
        end 
    else 
        return false 
    end 
end 

function make_lattice_node(L::Integer,n_grids::Integer,initial_potential::Real)

    #=

   =#

   default_potential=0
   if n_grids>0 && checkperfsquare(n_grids) 
    #    nsquare=√n_grids
       incre=L/√n_grids
       lattice_grid=[y==L ? Node(x,y,initial_potential,checkifedge(x,y,L)) : Node(x,y,default_potential,checkifedge(x,y,L)) for x=0-incre:incre:L+incre,y=0-incre:incre:L+incre]
       return Lattice(L,n_grids,lattice_grid)

   else 
       error("Use integer which is a perfect square\n")
       return 0
   end 
end 


function average_over_neighbors(grid::Array{Node},i,j,a)

    #=

    =#


    return 1/4a^2*(grid[i-1,j].potential+grid[i+1,j].potential+grid[i,j-1].potential+grid[i,j+1].potential)

end 

function relax(lattice::Lattice,L::Real,n_grids::Integer)

    #=

    =#

        m,n=size(lattice.lattice_grid)
        grid=lattice.lattice_grid
        a=L/√n_grids

        for i=2:n-1,j=2:m-1
            node=grid[i,j]
            # @show node
            if node.edge_flag==false
               node.potential=average_over_neighbors(grid,i,j,a)
            end 
        end 

end 

# function relax_dont(lattice::Lattice,trials::Real)

#     #=

#     =#

#     Δ=0
#     while Δ<trials  
#         # @show  Δ
#         relax(lattice) 
#         # @show lattice.lattice_grid
#         Δ=Δ+1
#     end
# end 

function topographical_map(lattice::Lattice)

    z_data=[node.potential for node in lattice.lattice_grid[2:end-1,2:end-1]]

    return surface(
        x=[node.x_ for node in lattice.lattice_grid[2:end-1,2:end-1]],
        y=[node.y_ for node in lattice.lattice_grid[2:end-1,2:end-1]],
        z=z_data,
        contours_z=attr(
            show=true,
            usecolormap=true,
            highlightcolor="limegreen",
            project_z=true
        )
    )

end

function animate(lattice::Lattice,trials::Integer,n_grids::Integer)

    trace = topographical_map(lattice)
    n_frames = trials
    frames  = Vector{PlotlyFrame}(undef, n_frames)
    for k in 1:n_frames
        # @show  Δ
        relax(lattice,lattice.length,n_grids) 
        frames[k]=frame(data=(attr(z=[node.potential for node in lattice.lattice_grid[2:end-1,2:end-1]])),
        layout=attr(title_text="Iteration $k"), #update title
        name="fr$k", #frame name; it is passed to slider 
        traces=[0] # this means that the above data update the first trace (here the unique one) 
        ) 
    end


    updatemenus = [attr(type="buttons", 
                        active=0,
                        y=1.2,  #(x,y) button position 
                        x=1.2,
                        buttons=[attr(label="Play",
                                    method="animate",
                                    args=[nothing,
                                            attr(frame=attr(duration=1, 
                                                            redraw=true),
                                                transition=attr(duration=0),
                                                fromcurrent=true,
                                                mode="immediate"
                                                            )])])];


    sliders = [attr(active=0, 
                    minorticklen=0,
                    
                    steps=[attr(label="f$k",
                                method="animate",
                                args=[["fr$k"], # match the frame[:name]
                                    attr(mode="immediate",
                                        transition=attr(duration=1),
                                        frame=attr(duration=1, 
                                                    redraw=true))
                                    ]) for k in 1:n_frames ]
                )];    

    layout = Layout(title_text="Finding Pontential Via Relaxation", title_x="x",
        width=1000, height=1000,
                xaxis_range=[0, lattice.length+5], 
                yaxis_range=[0, lattice.length+5],
                updatemenus=updatemenus,
                sliders=sliders)

    Plot(trace, layout, frames)

end

latt=make_lattice_node(16,16,10)
animate(latt,100,16)

  1. replace the frame definition by:
frames[k]=frame(data=[attr(z=[node.potential for node in lattice.lattice_grid[2:end-1,2:end-1]])],
                        layout=attr(title_text="Iteration $k"), #update title
                        name="fr$k", #frame name; it is passed to slider 
                        traces=[0] # this means that the above data update the first trace (here the unique one) 
        ) 

i.e. data is a vector.

  1. xaxis is a scene property, and scene is missing from your layout definition. The right definition is:
layout = Layout(title_text="Finding Pontential Via Relaxation", title_x="x",
        width=700, height=500,
                scene=attr(xaxis_range=[0, lattice.length+5], 
                yaxis_range=[0, lattice.length+5]),
                updatemenus=updatemenus,
                sliders=sliders)
  1. with these changes the animation works, but the surface z-variation is unnoticeable because, for example, if fig = Plot(trace, layout, frames)
    we have the difference:
fig.frames[50].data[1].z .- fig.frames[1].data[1].z    
5×5 Matrix{Real}:
 0  0           0           0           0
 0  4.00604e-5  0.00252263  0.002559    0
 0  4.12361e-5  0.00259905  0.00256199  0
 0  0           0.002522    7.94373e-5  0
 0  0           0           0           0

and

fig.frames[50].data[1].z .-fig.frames[49].data[1].z    
5×5 Matrix{Real}:
 0  0    0    0    0
 0  0.0  0.0  0.0  0
 0  0.0  0.0  0.0  0
 0  0    0.0  0.0  0
 0  0    0    0    0
1 Like

So it does work if one uses a large enough potential, for instance


latt=make_lattice_node(100,100,10234231234)
animate(latt,100,100)

Is there a way to save these plotlyjs animations in gif format?

From your trace and layout, modified to:

layout = Layout(title_text="Finding Pontential Via Relaxation", title_x=0.5,
                width=700, height=500,
                scene=attr(xaxis_range=[0, lattice.length+5], 
                yaxis_range=[0, lattice.length+5]),
                )

define:

fig=Plot(trace, layout)

remove the frames, updatemenus and sliders definition, and create the gif/mp4 as follows:

using Plots
fnames=String[]
for k in 1:n_frames
    relax(lattice,lattice.length,n_grids) 
    update!(fig, attr(z=[node.potential for node in lattice.lattice_grid[2:end-1,2:end-1]]),
                 layout=attr(title_text="Iteration $k")                    
                ) 
    filename=lpad(k, 6, "0")*".png"
    push!(fnames, filename)
    savefig(fig, "tmp/"*filename, width=700, height=500, scale=1) #tmp, a folder where the frames are saved
end
anim = Plots.Animation("tmp", fnames)
Plots.buildanimation(anim, "your.gif", fps = 12, show_msg=false)
#or Plots.buildanimation(anim, "your.mp4", fps = 12, show_msg=false)  
1 Like

This unfortunately does not work, the types do not coincide. The problem seems to be when one creates a trace with PlotLYJS that that conflicts with argument types for update!. Stack trace:


ERROR: TypeError: in keyword argument layout, expected Layout, got PlotlyBase.PlotlyAttribute{Dict{Symbol,Any}}

when I eliminate the layout line I get:

ERROR: 
MethodError: no method matching update!(::Plot{Array{GenericTrace{Dict{Symbol,Any}},1},Layout{Dict{Symbol,Any}},Array{PlotlyFrame,1}}, ::UnitRange{Int64}, ::PlotlyBase.PlotlyAttribute{Dict{Symbol,Any}}; layout=Layout{Dict{Symbol,Any}}(Dict{Symbol,Any}(:template => Template
  data: Dict{Symbol,Array{PlotlyBase.PlotlyAttribute{Dict{Symbol,Any}},1}}
  layout: PlotlyBase.PlotlyAttribute{Dict{Symbol,Any}}
,:margin => Dict(:l => 50,:b => 50,:r => 50,:t => 60)), Subplots
  rows: Int64 1
  cols: Int64 1
  shared_xaxes: Bool false
  shared_yaxes: Bool false
  start_cell: String "top-left"
  subplot_titles: Missing missing
  column_widths: Missing missing
  row_heights: Missing missing
  specs: Array{Spec}((1, 1))
  insets: Missing missing
  column_titles: Missing missing
  row_titles: Missing missing
  x_title: Missing missing
  y_title: Missing missing
  grid_ref: Array{Array{PlotlyBase.SubplotRef,1}}((1, 1))
  has_secondary_y: Bool false
  horizontal_spacing: Float64 0.2
  vertical_spacing: Float64 0.3
  max_width: Float64 1.0
  _widths: Array{Float64}((1,)) [1.0]
  _heights: Array{Float64}((1,)) [1.0]
))
Closest candidates are:
  update!(::Plot, ::Union{Int64, AbstractArray{Int64,1}}) at /Users/dlakhdar/.julia/packages/PlotlyBase/4NWbR/src/api.jl:226 got unsupported keyword argument "layout"
  update!(::Plot, ::Union{Int64, AbstractArray{Int64,1}}, ::AbstractDict; layout, kwargs...) at /Users/dlakhdar/.julia/packages/PlotlyBase/4NWbR/src/api.jl:226
  update!(::Plot, ::Any; layout, kwargs...) at /Users/dlakhdar/.julia/packages/PlotlyBase/4NWbR/src/api.jl:232
  ...
Stacktrace:
 [1] update!(::Plot{Array{GenericTrace{Dict{Symbol,Any}},1},Layout{Dict{Symbol,Any}},Array{PlotlyFrame,1}}, ::PlotlyBase.PlotlyAttribute{Dict{Symbol,Any}}; layout::Layout{Dict{Symbol,Any}}, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /Users/dlakhdar/.julia/packages/PlotlyBase/4NWbR/src/api.jl:232
 [2] update!(::Plot{Array{GenericTrace{Dict{Symbol,Any}},1},Layout{Dict{Symbol,Any}},Array{PlotlyFrame,1}}, ::PlotlyBase.PlotlyAttribute{Dict{Symbol,Any}}) at /Users/dlakhdar/.julia/packages/PlotlyBase/4NWbR/src/api.jl:232
 [3] top-level scope at REPL[35]

I don’t have any input on your actual problem, but a couple of unrelated pointers:

Unless you want your code to be unnecessarily slow, avoid abstract field types in your structs. No Real or Array{Node}. Either make the fields concrete or parametric.

This is a bit pointless

The comparison already returns true or false, so just write

return (√number * √number==number) 

I used your functions, and separate definition of trace and layout, without understanding what you really want to animate. It would have been useful to post a minimal example that requires update!
To avoid the displayed error define the basic figure as follows:

lattice = make_lattice_node(100, 100, 10234231234)
fig = Plot(topographical_map(lattice), 
           PlotlyJS.Layout(title_text="", title_x=0.5,
                width=700, height=500,
                scene=attr(xaxis_range=[0, lattice.length+5], 
                yaxis_range=[0, lattice.length+5]),
                ))

and by https://github.com/sglyon/PlotlyBase.jl/blob/master/src/api.jl#L231 update! should be defined as:

update!(fig, Dict(:z=>[[node.potential for node in lattice.lattice_grid[2:end-1,2:end-1]]]),
             layout=PlotlyJS.Layout(title_text="Iteration $k"))

In Dict(:z =>[[.....]]) the outermost [ ] are necessary because the data in Plot consists in a vector of traces, and here [ ] points out to update z in the unique trace from fig.

1 Like