Genie Discord forum

Author AvatarDaveMacMahon
3/7/2023, 12:06:25 AM

I can create plotly plots using the new GenieFramework API, but I cannot figure out how to get my app to react to plotly events such as hover or click. All of the tutorials I have found online that show this feature (primarily the otherwise great Oscars demo) use the "old" API with a user-defined subtype of ReactiveModel. I haven't found any that show how to do this using the "new" API (@handlers, @in, @out, etc.). How can add this functionality to my Genie(Builder) apps that use the new GenieFramework API? Is there a version of the Oscars demo that uses the new GenieFramework API?

Author Avatarhhaensel
3/7/2023, 11:18:44 PM

Most of it is very much the same:

  • place @mixin data::StipplePlotly.Charts.PlotlyEvents in the @app section
  • add @methods watchplots() to the code
  • add class=sync_data to the plot in the ui
Author Avatarhhaensel
3/7/2023, 11:19:51 PM

Unfortunately you cannot (yet) just use PlotlyEvents for the mixin (I don't know why)

Author AvatarDaveMacMahon
3/8/2023, 6:08:19 AM

Thanks for the suggestions, but it's still not working for me.

I am using GenieBuilder and my app.jl file doesn't have an @app section. should I just add @app @mixin data::StipplePlotly.Charts.PlotlyEvents before the @handlers section? Or maybe within or after???

When I used @methods watchplots() the web page only showed the Genie logo, but none of my UI. If I changed that to @mounted watchplots() I was able to see my UI. Does it matter where this goes in app.jl?

How do I actually get the events? I tried adding an @onchange data_click handler, but I got an undefined variable error. I added @in data_click and that made the error go away, but this handler was never called.

I did have the class="sync_data" added to the <plotly> tag.

Thanks again. I'll post an MWE if I don't get it working after clarifying these points.

Author AvatarDaveMacMahon
3/17/2023, 3:07:04 AM

I got a chance to look at this again, but I still can't get it to work. When the page loads, I can see this message in the browser console:

Syncing plot of class 'sync_data js-plotly-plot' to Main_App_varMain_App_ReactiveModel.data

When I click on a point in the plot, I can see that the plotly_click event is being handled in syncplot.js and it is setting model['data_click'] to filteredEventData.out which looks like what I would expect, but the @onchange data_click handler in app.jl never gets called.

Here is my current app.jl file:

module App

using GenieFramework
@genietools

# Using @methods instead of @mounted results in a blank page (genie logo only)
#@methods watchplots()
@mounted watchplots()

@handlers begin
  @out message = "Hello World!"
  @out layout = PlotLayout(title=PlotLayoutTitle(text="my plot"))
  @out data = PlotData[]
  @mixin data::StipplePlotly.Charts.PlotlyEvents
  # It seems like data_click should be defined by the @mixin,
  # but if I don't define it explicitly then the `@onchange data_click`
  # handler below throws `UndefVarError: data_click not defined`.
  @in data_click = Dict{String,Any}()

  @onchange isready begin
    @info "App is loaded"
    data = [ PlotData(x=1:50, y=rand(50)) ]
  end

  # This handler never gets called even though syncplot.js seems
  # to be setting `model['data_click'] to `filteredEventData.out`.
  @onchange data_click begin
    @info "data click" data_click
  end
end

@page("/", "app.jl.html")

end

Here's my app.jl.html file:

<h1>{{message}}</h1>
<plotly :data="data" :layout="layout" v-if="!isprocessing" class="sync_data"></plotly>

It seems like I'm missing something simple, but I can't figure out what it is.

Author AvatarPhreakit
3/18/2023, 2:59:52 PM

I'm stuck at exactly the same issue! Please let me know if you make any progress.

Author AvatarPhreakit
3/18/2023, 7:03:39 PM

I managed to solve it! It actually is a similar issue I had before, when the data change was not caught by the handlers. The issue is the definition of data_click, apparently most of these things are supposed to be arrays, so @in data_click = Dict{String, Any}[]. Modifying your example, we can delete points by clicking at them: ```julia module App

using GenieFramework @genietools

@mounted watchplots()

@handlers begin @out message = "Hello World!"

@out layout = PlotLayout(title=PlotLayoutTitle(text="my plot")) @out data = PlotData @out x_p = collect(1:50) @out y_p = rand(50)

@in data_click = Dict{String, Any}

@onchange isready begin @info "App is loaded" data = PlotData(x=x_p, y=y_p) end

This handler never gets called even though syncplot.js seems

to be setting model['data_click'] to filteredEventData.out`.

@onchange data_click begin @info "data click" data_click num_del = data_click[]["points"][]"pointNumber"+1 deleteat!(x_p, num_del) deleteat!(y_p, num_del)

data = [ PlotData(x=x_p, y=y_p) ]

end

end

@page("/", "app.jl.html")

end```

Author Avatarabhimanyuaryan
3/18/2023, 7:59:01 PM

I also need to check how to adapt this to new api

Author AvatarDaveMacMahon
3/19/2023, 9:44:46 AM

Thank you, @Phreakit ! That's exactly my problem. Changing data_click to a Vector got things working. I knew it must have been something simple. One additional detail to be aware of is that the pointIndex and curveNumber fields are 0-based rather than 1-based.

Author AvatarDaveMacMahon
3/19/2023, 9:47:29 AM

Thanks for the pointer to the example, @abhimanyuaryan, but it turns out that the "old way" has data_click being a Dict{String,Any} rather than a Vector{Dict{String,Any}} so it wouldn't have helped me with the problem I was encountering.

Author AvatarDaveMacMahon
3/19/2023, 4:38:56 PM

Just to emphasize this aspect of the example from @Phreakit: the @mixin macro is not needed.

Author Avatarabhimanyuaryan
3/21/2023, 1:45:54 PM

I will push the fix by tomorrow. I got the event thingy working

Author Avatarabhimanyuaryan
3/22/2023, 1:11:50 PM

can you use this PR: https://github.com/GenieFramework/StipplePlotly.jl/pull/60

and use dev it??? And use it with newer API like this

using GenieFramework

@genietools

@vars Example begin
    plot1::R{Plot} = Plot()
    plot1_selected::R{Dict{String, Any}} = Dict{String, Any}()
    plot1_hover::R{Dict{String, Any}} = Dict{String, Any}()

    plot2::R{Plot} = Plot()
    plot2_selected::R{Dict{String, Any}} = Dict{String, Any}()
    plot2_hover::R{Dict{String, Any}} = Dict{String, Any}()
end

Genie.Router.delete!(:Example)

function ui(model::Example)
    page(model, class = "container", 
    # append = script([
    #     watchplot("plot1", model),
    #     watchplot("plot2", model)
    # ]),
    row(class = "st-module", [
        plotly(:plot1, id = "plot1"),
        plotly(:plot2, id = "plot2")
    ]))
end

Stipple.js_mounted(::Example) = join([
    watchplot(:plot1),
    watchplot(:plot2)
])

model = init(Example, debounce=0)

route("/") do
    model |> handlers |> ui |> html
end

function handlers(model)
    on(model.isready) do isready
        isready || return
        push!(model)
    end

    on(model.plot1_selected) do data
        model.plot2.data[1][:selectedpoints] = getindex.(data["points"], "pointIndex")
        notify(model.plot2)
    end

    on(model.plot2_selected) do data
        model.plot1.data[1][:selectedpoints] = getindex.(data["points"], "pointIndex")
        notify(model.plot1)
    end

    on(model.plot1_selected) do data
        haskey(data, "points") && @info "Selection: $(getindex.(data["points"], "pointIndex"))"
    end

    return model
end

up(8000)

for i in 1:3
    model.plot1[] = Plot(scatter(y = rand(5)))
    model.plot2[] = Plot(scatter(y = rand(5)))
    sleep(0.1)
end