-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
496 additions
and
454 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
function rectselect(ax) | ||
selrect, h = select_vspan(ax.scene; color = (0.9)) | ||
translate!(h, 0, 0, -1) # move to background | ||
return selrect | ||
end | ||
|
||
function Bonito.jsrender(s::Session, selector::SelectSet) | ||
rows = map(selector.items[]) do value | ||
c = Bonito.Checkbox(true; class = "p-1 m-1") | ||
on(s, c.value) do x | ||
values = selector.value[] | ||
has_item = (value in values) | ||
if x | ||
!has_item && push!(values, value) | ||
else | ||
has_item && filter!(x -> x != value, values) | ||
end | ||
notify(selector.value) | ||
end | ||
return Row(value, c) | ||
end | ||
return Bonito.jsrender(s, Card(Col(rows...))) | ||
end | ||
|
||
function style_map(::AbstractRange{<:Number}) | ||
return Dict( | ||
:color => identity, | ||
:colormap => RGBAf.(Colors.color.(to_colormap(:lighttest)), 0.5), | ||
) | ||
end | ||
|
||
function style_map(values::Set) | ||
mpalette = [:circle, :star4, :xcross, :diamond] | ||
dict = Dict(v => mpalette[i] for (i, v) in enumerate(values)) | ||
mcmap = Makie.wong_colors(0.5) | ||
mcolor_lookup = Dict(v => mcmap[i] for (i, v) in enumerate(values)) | ||
return Dict(:marker => v -> dict[v], :marker_color => mcolor_lookup) | ||
end | ||
|
||
function select_vspan(scene; blocking = false, priority = 2, kwargs...) | ||
key = Mouse.left | ||
waspressed = Observable(false) | ||
rect = Observable(Rectf(0, 0, 1, 1)) # plotted rectangle | ||
rect_ret = Observable(Rectf(0, 0, 1, 1)) # returned rectangle | ||
|
||
# Create an initially hidden rectangle | ||
low = Observable(0.0f0) | ||
high = Observable(0.0f0) | ||
|
||
on(rect) do r | ||
low.val = r.origin[1] | ||
high[] = r.origin[1] + r.widths[1] | ||
end | ||
plotted_span = vspan!( | ||
scene, | ||
low, | ||
high, | ||
visible = false, | ||
kwargs..., | ||
transparency = true, | ||
color = (:black, 0.1), | ||
) | ||
|
||
on(events(scene).mousebutton, priority = priority) do event | ||
if event.button == key | ||
if event.action == Mouse.press && is_mouseinside(scene) | ||
mp = mouseposition(scene) | ||
waspressed[] = true | ||
plotted_span[:visible] = true # start displaying | ||
rect[] = Rectf(mp, 0.0, 0.0) | ||
return Consume(blocking) | ||
end | ||
end | ||
if !(event.button == key && event.action == Mouse.press) | ||
if waspressed[] # User has selected the rectangle | ||
waspressed[] = false | ||
r = Makie.absrect(rect[]) | ||
w, h = widths(r) | ||
rect_ret[] = r | ||
end | ||
# always hide if not the right key is pressed | ||
#plotted_span[:visible] = false # make the plotted rectangle invisible | ||
return Consume(blocking) | ||
end | ||
|
||
return Consume(false) | ||
end | ||
on(events(scene).mouseposition, priority = priority) do event | ||
if waspressed[] | ||
mp = mouseposition(scene) | ||
mini = minimum(rect[]) | ||
rect[] = Rectf(mini, mp - mini) | ||
return Consume(blocking) | ||
end | ||
return Consume(false) | ||
end | ||
|
||
return rect_ret, plotted_span | ||
end | ||
|
||
function Bonito.jsrender(s::Session, selector::SelectSet) | ||
rows = map(selector.items[]) do value | ||
c = Bonito.Checkbox(true; class = "p-1 m-1") | ||
on(s, c.value) do x | ||
values = selector.value[] | ||
has_item = (value in values) | ||
if x | ||
!has_item && push!(values, value) | ||
else | ||
has_item && filter!(x -> x != value, values) | ||
end | ||
notify(selector.value) | ||
end | ||
return Row(value, c) | ||
end | ||
return Bonito.jsrender(s, Card(Col(rows...))) | ||
end | ||
|
||
function variable_legend(name, values::AbstractRange{<:Number}, palette::Dict) | ||
range, cmap = palette[:colormap] | ||
return S.Colorbar(limits = range, colormap = cmap, label = string(name)) | ||
end | ||
|
||
function variable_legend(name, values::Set, palette::Dict) | ||
marker_color_lookup = (x) -> begin | ||
if haskey(palette, :color) | ||
return get(palette[:color], x, :black) | ||
else | ||
return :black | ||
end | ||
end | ||
marker_lookup = (x) -> begin | ||
if haskey(palette, :marker) | ||
return palette[:marker][x] | ||
else | ||
return :rect | ||
end | ||
end | ||
conditions = collect(values) | ||
elements = map(conditions) do c | ||
return MarkerElement(marker = marker_lookup(c), color = marker_color_lookup(c)) | ||
end | ||
return S.Legend(elements, conditions) | ||
end | ||
|
||
|
||
widget_value(w::Vector{<:String}; resolution = 1) = w | ||
widget_value(x::Vector; resolution = 1) = | ||
x[1] ≈ x[end] ? Float64[] : range(Float64(x[1]), Float64(x[end]), length = 5) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
""" | ||
explore(model::UnfoldModel; positions = nothing, size = (700, 600)) | ||
Run the dashboard for explorative ERP analysis. | ||
Arguments:\\ | ||
- `model::UnfoldLinearModel{Float64}` - Unfold linear model with categorical and continuous terms. | ||
- `positions::Vector{Point{2, Float32}}` - x an y coordinates of the channels. | ||
- `size::Tuple{Float64, Float64}` - size of the topoplot panel. | ||
Actions: | ||
- Extract formula terms and itheir features. | ||
- Create interactive formula with checkboxes. | ||
- Arrange and map dropdown menus. | ||
- Create interactive topoplot. | ||
- Create Observable DataFrame with predicted values (yhats) and more. | ||
- Create `GridLayout`. | ||
- Use `Base.ReentrantLock`, a synchronization primitive_ to manage concurrent access to shared resources in multi-threaded programs | ||
- Create Figure. | ||
- Translate into into HTML code using DOMs. | ||
**Return Value:** `Hyperscript.Node{Hyperscript.HTMLSVG}` - final HTML code of the dashboard. | ||
""" | ||
function explore(model::UnfoldModel; positions = nothing, size = (700, 600)) | ||
App() do | ||
variables = extract_variables(model) | ||
widget_checkbox, widget_signal, widget_dom, value_ranges = | ||
formular_widgets(variables) | ||
|
||
var_types = map(x -> x[2][3], variables) | ||
varnames = first.(variables) | ||
|
||
mapping, mapping_dom = mapping_dropdowns(varnames, var_types) | ||
|
||
if isnothing(positions) | ||
channel = Observable(1) | ||
topo_widget = nothing | ||
else | ||
topo_widget, channel = topoplot_widget(positions; size = size .* 0.5) | ||
end | ||
|
||
yhats_sig = yhats_signal(model, widget_signal, channel) | ||
on(mapping) do m | ||
ws = widget_signal.val | ||
ks_m = values(m) | ||
ks_ws = [w.first for w in ws] | ||
for k in ks_ws | ||
widget_checkbox[k][] = k ∈ ks_m | ||
end | ||
end | ||
|
||
obs = Observable(S.GridLayout()) | ||
l = Base.ReentrantLock() | ||
|
||
Makie.onany_latest(yhats_sig, mapping; update = true) do eff, mapping # update = true means only, that it is run once immediately | ||
lock(l) do | ||
obs[] = plot_data( | ||
eff, | ||
value_ranges, | ||
varnames[var_types.==:CategoricalTerm], | ||
varnames[var_types.==:ContinuousTerm], | ||
mapping, | ||
) | ||
end | ||
return | ||
end | ||
css = Asset(joinpath(@__DIR__, "..", "style.css")) | ||
fig = plot(obs; figure = (size = size,)) | ||
res = DOM.div( | ||
css, | ||
Bonito.TailwindCSS, | ||
Grid( | ||
Card(widget_dom, style = Styles("grid-area" => "header")), | ||
Card(mapping_dom, style = Styles("grid-area" => "sidebar")), | ||
Card(topo_widget, style = Styles("grid-area" => "topo")), | ||
Card(fig, style = Styles("grid-area" => "content")); | ||
columns = "5fr 1fr", | ||
rows = "1fr 6fr 4fr", | ||
areas = """ | ||
'header header' | ||
'content sidebar' | ||
'content topo' | ||
""", | ||
); | ||
style = Styles( | ||
"height" => "$(1.2*size[2])px", | ||
"width" => "$(size[1])px", | ||
"margin" => "20px", | ||
"position" => :relative, | ||
), | ||
) | ||
return res | ||
end | ||
end |
Oops, something went wrong.