Genie Discord forum

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?

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

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

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.

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.

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

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```

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

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.

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.

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

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

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