Webpage with reactive plot animation

I’d like to have a webpage with a plot which gets updated automatically every second, and which depends on some user input. Schematically, something like:

julia> xs = [0]; vx = ReactiveSlider(initial_value=1)
       while true
           push!(xs, xs[end]+vx[])  
           println(x)
           sleep(1)
           plot!(xs)   # update plot
       end

Or, for a concrete example, “Here is a plot of how much distance the car has traveled so far (x). Adjust the slider value to adjust the speed of the car (vx) in real time”

I was able to get this working in Pluto. But as far as I know, one can still not easily serve a (dynamic, julia-process-backed) notebook as a webpage that multiple people can access simultaneously, right? What would be my best option for this? Dash? Genie? Dash being stateless makes it sound messy (from my limited experience).

2 Likes

Did you have a look at Bonito.jl and Oxygen.jl?

3 Likes

Thank you for the recommendation! I got something working by tweaking a Bonito.jl example:

using Bonito, Bonito.Observables, Base.Threads

import Bonito.TailwindDashboard as D

function create_svg(sl_nsamples, sl_sample_step, sl_phase, sl_radii, color)
    width, height = 900, 300
    cxs_unscaled = [i*sl_sample_step + sl_phase for i in 1:sl_nsamples]
    cys = sin.(cxs_unscaled) .* height/3 .+ height/2
    cxs = cxs_unscaled .* width/4pi
    rr = sl_radii
    # DOM.div/svg/etc is just a convenience in Bonito for using Hyperscript, but circle isn't wrapped like that yet
    geom = [SVG.circle(cx=cxs[i], cy=cys[i], r=rr, fill=color(i)) for i in 1:sl_nsamples[]]
    return SVG.svg(SVG.g(geom...);
        width=width, height=height
    )
end

obs_phase = Observable(1.0)

app = App() do session
    colors = ["black", "gray", "silver", "maroon", "red", "olive", "yellow", "green", "lime", "teal", "aqua", "navy", "blue", "purple", "fuchsia"]
    color(i) = colors[i%length(colors)+1]
    sl_nsamples = D.Slider("nsamples", 1:200, value=100)
    sl_sample_step = D.Slider("sample step", 0.01:0.01:1.0, value=0.1)
    sl_phase = D.Slider("phase", 0.0:0.1:6.0, value=0.0)
    sl_radii = D.Slider("radii", 0.1:0.1:60, value=10.0)
    svg = map(create_svg, sl_nsamples.value, sl_sample_step.value, obs_phase, sl_radii.value, color)
    return DOM.div(D.FlexRow(D.FlexCol(sl_nsamples, sl_sample_step, obs_phase, sl_radii), svg))
end

@spawn for v in -10:0.01:10
    obs_phase[] = v
    sleep(0.01)
end

app

It’s quite straight-forward and it looks cool, props to @sdanisch !

Not quite sure how to make that logic work out per-user (maybe just moving the spawn and observable inside of the do), but I’ll figure it out.

1 Like

I’ve made a couple of interactive pages using what I think is a pretty general solution. It depends on HTTP.jl for communication and no web frameworks. Here’s the second one, that I just completed: 2D Vortex Dynamics.

You mentioned automatic updates of a plot, so my approach might be useful for you, because I use websockets, which let you continuously stream data from the Julia process into the page.

Another solution is the plutosliderserver.

1 Like