plotly / Dash.jl

Dash for Julia - A Julia interface to the Dash ecosystem for creating analytic web applications in Julia. No JavaScript required.
MIT License
483 stars 40 forks source link

Can Plotly Dash animations display buttons and sliders? #189

Open 00krishna opened 1 year ago

00krishna commented 1 year ago

I am working on creating some dash animation. Now I am able to create some animations using PlotlyJS, but I cannot seem to get that same buttons and sliders to appear on a Dash webpage.

I was following this animation example, and the plots appear but the animation does not seem to appear--nor the buttons. In the code below I just have a button for the animation, but I also have other examples of PlotlyJS code with sliders.

One solution is to basically just generate the animation frames in the background, save them to a dcc_store, and then write my own play button and slider controls--using existing plotly components--to pull the frames from the store and display them. I can certainly do that, but was not sure if that is a good idea. The primary concern would be speed or performance on this kind of manual animation. So I was not sure if should write the manual method, or I should rely on those internally generated buttons using updatemenus and the slider layout argument in Plotly.

I have the code below, so I was hoping that someone might indicate whether the functionality to display this animation is not yet available, or whether I have coded this example incorrectly. Note that I am not getting any errors on in the webpage debug indicators or in the terminal.

I posted a similar question to this on the community forum. In that case I was just asking whether someone knew whether this code should work or whether I should post an issue about it. But no one has responded to that message yet, so I figured I would just post to the Issues list.

using Dash, Printf, PlotlyJS

app = dash()
app.layout = html_div() do
    html_div(html_button("Plot", id="b1")),
    html_div(dcc_graph(id="g1"))
end

callback!(
    app,
    Output("g1", "figure"),
    Input("b1", "n_clicks")
) do nc

    N=200
    xs1 = LinRange(0, 35, N)
    ys1 = sin.(xs1)
    ys2 = cos.(xs1)
    ys3 = sin.(2 .* xs1) 

    trace1 = scatter(;x = xs1,  
                y = ys1,
                mode="lines",
                line_width=1.5,
                line_color="RoyalBlue")

    trace2 = scatter(;x = xs1,  
                y = ys2,
                mode="lines",
                line_width=1.5)

    trace3 = scatter(;x = xs1,  
                y = ys3,
                mode="lines",
                line_width=1.5)

    trace4 = scatter(;x = xs1,  
                y = ys3,
                mode="lines",
                line_width=1.5)

    # animations 

    frames = PlotlyFrame[]

    x_inc = LinRange(0, 8*pi, N)

    for i in eachindex(x_inc)
        b = sin(x_inc[i])
        ys4 = ys1 .+ b
        push!(frames, 
                frame(name="a$(i)", data = [attr(x=xs1, y=ys4)], 
                traces=[3]))
    end

    layout = Layout(;
        yaxis_range = [-5, 10],
        xaxis_type = "linear",
        xaxis_title = "timesteps",
        yaxis_title = "score",
        title="Optimization Example",
        legend_x = 1,
        legend_y = 1,
        hovermode = "closest",
        hoverlabel=(font=(size=10,),font_family="Rockwell"),
        transition_duration = 5,
        updatemenus=[
            (   visible=true,
                type="buttons",
                buttons=[(label="Animate", method="animate", args=[nothing])]
            )
        ]
    )

    return Plot([trace1, trace2, trace3, trace4], layout, frames) 

end;

run_server(app, "0.0.0.0", 8106, debug=true)
etpinard commented 1 year ago

Thanks for reporting!

This should work. Dash.jl and PlotlyJS.jl use the same plotting backend.

What's most likely happening is that PlotlyJS.Plot put the array of PlotlyFrame elements somewhere that JSON3.write does not know about.

We could try to patch

https://github.com/plotly/Dash.jl/blob/486625978203f89e7d90e0c699952ec71df11c88/src/plotly_js.jl#L3-L7

accordingly.

00krishna commented 1 year ago

@etpinard Oh thanks for the response. Okay, so is this code that would go into a Dash.jl pull request or something that I can just add to my own code to override the existing dispatch?

Should I try and submit this code change as a PR?

etpinard commented 1 year ago

Yep, ideally it would go in a Dash.jl pull request. This is a bug we want to fix!

00krishna commented 1 year ago

I can submit a PR, no problem. Is there a test case that I should add to try this, or do I not need to add an additional test case. I have not submitted a PR to Dash.jl before, so just want to make sure I do it correctly.

etpinard commented 1 year ago

Is there a test case that I should add to try this,

Unfortunately, similar PRs https://github.com/plotly/Dash.jl/pull/143 and https://github.com/plotly/Dash.jl/pull/166 relied on integration tests to lock down their patches.

If you can get the integration test running on your machine, adding a similar integration test file would be fine.

Otherwise, a new @testset block in test/core.jl would be great.

00krishna commented 1 year ago

@etpinard say, as a temporary workaround, can I just write my own Play button and Slider using Dash components? I can compute the frames and save them in a Store, and then load them manually.

I was not sure how to update a frame in a plot though--is that possible? I only have 1 animated trace out and 3 static traces in my plot. If I just update the data in the Plotly Graph Object, then I imagine that is the best way to do it--instead of repainting the whole plot. I can try that as a temporary workaround as I try and get the main issue fixed.