Figure dashboard with Bonito and WGLMakie

Hello everyone,

I am currently trying to set up a dashboard of figures using the following code

using Bonito
using WGLMakie
all_figs = Dict()
function newfig(title)
  fig = Figure()
  if title in keys(all_figs)
    all_figs[title][] = fig
  else
    all_figs[title] = Observable(fig)
  end
  fig
end
fig = newfig("rand");
ax = Axis(fig[1,1])
lines!(ax,randn(10))

App() do
    content = Any[]
    for t in keys(all_figs)
        push!(content,DOM.h2(t))
        push!(content,all_figs[t])
    end
    return Col(content...)
end

fig = newfig("rand");
#sleep(0.01) # <--without it, it fails because of timeout in evaljs_value
ax = Axis(fig[1,1])
lines!(ax,randn(10))

Without the small time delay between the creation of the figure and the axis the code fail with the following error:

ERROR: Timed out
Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:44
  [2] evaljs_value(session::Session{Bonito.SubConnection}, js::Bonito.JSCode; error_on_closed::Bool, timeout::Int64)
    @ Bonito ~/.julia/packages/Bonito/9fokY/src/session.jl:348
  [3] evaljs_value
    @ ~/.julia/packages/Bonito/9fokY/src/session.jl:294 [inlined]
  [4] insert_plot!(session::Session{Bonito.SubConnection}, scene::Scene, plot::Plot)
    @ WGLMakie ~/.julia/packages/WGLMakie/FbMOf/src/display.jl:469
  [5] insert!(screen::WGLMakie.Screen, scene::Scene, plot::Plot)
    @ WGLMakie ~/.julia/packages/WGLMakie/FbMOf/src/display.jl:480
  [6] push!(scene::Scene, plot::Plot)
    @ Makie ~/.julia/packages/Makie/4JW9B/src/scenes.jl:515
  [7] plot!(scene::Scene, plot::Poly{Tuple{GeometryBasics.HyperRectangle{2, Int64}}})
    @ Makie ~/.julia/packages/Makie/4JW9B/src/interfaces.jl:212
  [8] _create_plot!(F::Function, attributes::Dict{…}, scene::Scene, args::Observable{…})
    @ Makie ~/.julia/packages/Makie/4JW9B/src/figureplotting.jl:411
  [9] poly!(::Scene, ::Vararg{…}; kw::@Kwargs{…})
    @ Makie ~/.julia/packages/Makie/4JW9B/src/recipes.jl:521
 [10] poly!
    @ ~/.julia/packages/Makie/4JW9B/src/recipes.jl:519 [inlined]
 [11] initialize_block!(ax::Axis; palette::Nothing)
    @ Makie ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks/axis.jl:196
 [12] initialize_block!(ax::Axis)
    @ Makie ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks/axis.jl:154
 [13] _block(T::Type{…}, fig_or_scene::Figure, args::Vector{…}, kwdict::Dict{…}, bbox::Nothing; kwdict_complete::Bool)
    @ Makie ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks.jl:405
 [14] _block
    @ ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks.jl:321 [inlined]
 [15] #_block#1568
    @ ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks.jl:266 [inlined]
 [16] _block(::Type{Axis}, ::Figure)
    @ Makie ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks.jl:265
 [17] _block(::Type{Axis}, ::GridPosition; kwargs::@Kwargs{})
    @ Makie ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks.jl:260
 [18] _block
    @ ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks.jl:251 [inlined]
 [19] #_#1566
    @ ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks.jl:242 [inlined]
 [20] Axis(args::GridPosition)
    @ Makie ~/.julia/packages/Makie/4JW9B/src/makielayout/blocks.jl:241
 [21] top-level scope
    @ REPL[528]:1
Some type information was truncated. Use `show(err)` to see complete types.

I would like to know if this time delay is necessary or if I am not approaching the problem in the right way.

Thank you in advance !

You’re creating lots of global state.
That’s not per se bad, but definitely makes it hard to see what’s actually going on in your example.
Also, displaying a scene that is also inside an app is not a great idea.
Can you make an MWE (minimal working example) of what you’re trying to do without creating all this global state?

Thank you for your answer (and the great work on Bonito and Makie btw !). I have put below a minimal working example:

using Bonito
using WGLMakie
fig = Figure()
ax  = Axis(fig[1, 1])
lines!(ax, randn(1000))
store = Observable(fig)
App() do
    return Col(store)
end
# Update the figure later
fig = Figure()
store[] = fig
ax = Axis(fig[1, 1])
lines!(ax, randn(1000), color = :red)

On Firefox I sometimes get timeout errors, but I haven’t been able to reproduce them consistently.

When I am experimenting with a script I want the different figures to be accessible in a dashboard, and then be able to iteratively improve them in the REPL by re-evaluating part of the script (that is why in my initial post I am using a dictionary that store the different figures).

This workflow helps me keep a live dashboard that stays in sync with my script. I prefer that instead of traditional notebook (Pluto, Jupyter, BonitoBook) where you have to scroll through all your notebook to visualize the different figures.

Do you know an alternative way to do that ? Thanks in advance.

Would maybe BonitoBook with auto updates on file change do this for you?
That’s planned and shouldn’t be too hard to implement.

Otherwise, your main problem is, that putting a figure into the dom of an App, doesn’t block until the plot is fully displayed - display(fig) does that to avoid your issue.
What you could do is something like this:

all_figs = Dict()
function get_fig(key)
    fig = all_figs[key] 
    # This should block until the scene is fully displayed
    WGLMakie.get_screen_session(Makie.getscreen(Makie.get_scene(fig)))
   return fig
end

It also seems to me like the all_figs should be an observable, so that the dashboard automatically updates?

Thank you for your help !

Not in my case I think, I tend to “save on write” my scripts (e.g. in nvim and helix) when editing and I suspect it will result in unnecessary computations. I would prefer to manually execute parts of it.

I tried but WGLMakie.get_screen_session(Makie.getscreen(Makie.get_scene(fig.val))) (considering that fig is observable here) is failing as fig.scene is nothing.

Indeed ! But I didn’t find a solution yet. Also, it might be better to use a OrderedDict and update on new insertion.

I was thinking of using a Scene as a dashboard but I guess individual plot update will re-render every other plots ?