Skip to content

Commit 5369cb0

Browse files
committed
more
1 parent bb08b81 commit 5369cb0

File tree

6 files changed

+110
-45
lines changed

6 files changed

+110
-45
lines changed

scripts/runner.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,5 @@ begin
5252
for i in docs_entries
5353
format_file(i)
5454
end
55-
cd(".")
55+
cd("../")
5656
end

src/ERPExplorer.jl

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,27 @@ include("functions.jl")
2222
include("widgets.jl")
2323
include("plot_data.jl")
2424

25+
"""
26+
explore(model::UnfoldModel; positions = nothing, size = (700, 600))
27+
Run the dashboard for explorative ERP analysis.
28+
29+
Arguments:\\
30+
- `model::UnfoldLinearModel{Float64}` - Unfold linear model with categorical and continuous terms.
31+
- `positions::Vector{Point{2, Float32}}` - x an y coordinates of the channels.
32+
- `size::Tuple{Float64, Float64}` - size of the topoplot panel.
33+
34+
Actions:
35+
- Extract formula terms and itheir features.
36+
- Create interactive formula with checkboxes.
37+
- Arrange and map dropdown menus.
38+
- Create interactive topoplot.
39+
- Create Observable DataFrame with predicted values (yhats) and more.
40+
- Create `GridLayout`.
41+
- Use `Base.ReentrantLock`, a synchronization primitive_ to manage concurrent access to shared resources in multi-threaded programs
42+
43+
44+
**Return Value:** `res::Hyperscript.Node{Hyperscript.HTMLSVG}` - final HTML code of the dashboard.
45+
"""
2546
function explore(model::UnfoldModel; positions = nothing, size = (700, 600))
2647
App() do
2748
variables = extract_variables(model)
@@ -39,9 +60,8 @@ function explore(model::UnfoldModel; positions = nothing, size = (700, 600))
3960
else
4061
topo_widget, channel = topoplot_widget(positions; size = size .* 0.5)
4162
end
42-
#@debug "mapping" mapping
43-
#mapping = Observable(Dict(:color => :color, :fruit => :marker))
44-
eff_signal = effects_signal(model, widget_signal, channel)
63+
64+
yhats_sig = yhats_signal(model, widget_signal, channel)
4565
on(mapping) do m
4666
ws = widget_signal.val
4767
ks_m = values(m)
@@ -50,11 +70,12 @@ function explore(model::UnfoldModel; positions = nothing, size = (700, 600))
5070
widget_checkbox[k][] = k ks_m
5171
end
5272
end
73+
5374
obs = Observable(S.GridLayout())
5475
l = Base.ReentrantLock()
55-
Makie.onany_latest(eff_signal, mapping; update = true) do eff, mapping # update = true means only, that it is run once immediately
76+
77+
Makie.onany_latest(yhats_sig, mapping; update = true) do eff, mapping # update = true means only, that it is run once immediately
5678
lock(l) do
57-
#var_types = map(x -> x[2][3], variables)
5879
obs[] = plot_data(
5980
eff,
6081
value_ranges,
@@ -67,7 +88,7 @@ function explore(model::UnfoldModel; positions = nothing, size = (700, 600))
6788
end
6889
css = Asset(joinpath(@__DIR__, "..", "style.css"))
6990
fig = plot(obs; figure = (size = size,))
70-
return DOM.div(
91+
res = DOM.div(
7192
css,
7293
Bonito.TailwindCSS,
7394
Grid(
@@ -90,6 +111,7 @@ function explore(model::UnfoldModel; positions = nothing, size = (700, 600))
90111
"position" => :relative,
91112
),
92113
)
114+
return res
93115
end
94116
end
95117
export explore

src/functions.jl

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Actions:
4646
- `widget_checkbox`: Dictionary with the current values of the widgets (term => values).
4747
- `widget_signal`: widget_checkbox but as Observable, a signal that emits a dictionary with the current values of the widgets.
4848
- `formular_widget`: The HTML element that can be displayed to interact with the the widgets.
49-
- `value_ranges`: A dictionaryy containing the value ranges of each formula term.
49+
- `value_ranges`: A dictionary containing the value ranges of each formula term.
5050
"""
5151
function formular_widgets(variables)
5252
value_ranges = [k => value_range(v) for (k, v) in variables]
@@ -58,13 +58,18 @@ function formular_widgets(variables)
5858
c = checkboxes[k]
5959
w_cat_term = widgets[k].first
6060
w_value = widgets[k].second
61-
push!(widget_names, formular_text("+"), formular_text(c), dropdown(w_cat_term, w_value))
61+
push!(
62+
widget_names,
63+
formular_text("+"),
64+
formular_text(c),
65+
dropdown(w_cat_term, w_value),
66+
)
6267
end
63-
68+
6469
formular_widget = Row(widget_names...)
6570
widget_values = map(nw -> nw[2].value, widgets)
6671
checkbox_values = map(c -> c.value, checkboxes)
67-
72+
6873
widget_signal =
6974
lift(widget_values..., checkbox_values...; ignore_equal_values = true) do args...
7075
result = []
@@ -77,34 +82,49 @@ function formular_widgets(variables)
7782
end
7883
widget_checkbox = Dict(k => c for (c, (k, v)) in zip(checkbox_values, variables))
7984

80-
return widget_checkbox,
81-
widget_signal,
82-
formular_widget,
83-
value_ranges
85+
return widget_checkbox, widget_signal, formular_widget, value_ranges
8486
end
8587

86-
function effects_signal(model, widget_signal, channel)
87-
effects_signal = Observable{Any}(nothing; ignore_equal_values = true)
88+
"""
89+
yhats_signal(model, widget_signal, channel)
90+
Creates a dictionary with yhat values and more.\\
8891
89-
onany(widget_signal, channel; update = true) do widget_values, chan
92+
Arguments:\\
93+
- `model::UnfoldLinearModel{Float64}` - vector of key-value pairs with information about the model formula terms.
94+
- `widget_signal::Observable{Vector{Any}}` - a signal that emits a dictionary with the current values of the widgets.
95+
- `channel::Observable{Int64}` - number of selected channel-
96+
97+
Actions:
98+
- Compute predicted value (yhat) of the given model using `effects`.
99+
- Create `DataFrame` with columns: yhat, channel, dummy, time, eventname and unique columns for each formula term.
100+
- Make it Observable.
101+
102+
**Return Value:** `yhats_signal::Observable{Any}` containing DataFrame with yhats.
103+
"""
104+
function yhats_signal(model, widget_signal, channel)
105+
106+
yhats_signal = Observable{Any}(nothing; ignore_equal_values = true)
90107

91-
effect_dict = Dict(
108+
onany(widget_signal, channel; update = true) do widget_values, chan
109+
yhat_dict = Dict(
92110
k => widget_value(wv[2]) for (k, wv) in widget_values if !isempty(wv) && wv[1]
93111
)
94-
if isempty(effect_dict)
95-
effect_dict = Dict(:dummy => ["dummy"])
112+
if isempty(yhat_dict)
113+
yhat_dict = Dict(:dummy => ["dummy"])
96114
end
97-
eff = effects(effect_dict, model)
115+
yhats = effects(yhat_dict, model)
116+
98117
for (k, wv) in widget_values
99118
if isempty(wv[2]) || !wv[1]
100-
eff[!, k] .= "typical_value"
119+
yhats[!, k] .= "typical_value"
101120
end
102121
end
103122

104-
filter!(x -> x.channel == chan, eff)
105-
effects_signal[] = eff
123+
filter!(x -> x.channel == chan, yhats)
124+
yhats_signal[] = yhats
106125
end
107-
return effects_signal
126+
127+
return yhats_signal
108128
end
109129

110130

src/plot_data.jl

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11

22
"""
33
plot_data(data, value_ranges, categorical_vars, continuous_vars, mapping_obs)
4-
- `data`: effects(Dict(...), m)
5-
- `value_ranges`:
6-
- `categorical_vars`:
7-
- `continuous_vars`:
8-
- `mapping`: Dict name=>property.
4+
Plotting an interactive dashboard.
95
10-
**Return Value:** `DataFrames`.
6+
Action:
7+
- Create default palletes for colors, markers, line styles and colorstyles for continuous values.
8+
9+
Arguments:
10+
- `data::DataFrame` - the result of `effects(Dict(...), model) ` with columns: yhat, channel, dummy, time, eventname and unique columns for each formula term..
11+
- `value_ranges::Vector{Pair{Symbol}}` - value range for continuous variables, levels for categorical.
12+
- `categorical_vars::Vector{Symbol}` - categorical terms.
13+
- `continuous_vars::Vector{Symbol}` - continuous terms.
14+
- `mapping::Dict{Symbol, Symbol}` - dictionary with dropdown menus and their default values.
15+
16+
**Return Value:** `Makie.GridLayoutSpec`.
1117
"""
1218
function plot_data(data, value_ranges, cat_terms, continuous_vars, mapping_obs)
13-
#mapping = Dict(:color => :color, :fruit => :marker)#, :fruit => :linestyle) will work in Makie 0.21
1419
mapping = to_value(mapping_obs)
1520

1621
mpalette = [:circle, :xcross, :star4, :diamond]
@@ -23,7 +28,7 @@ function plot_data(data, value_ranges, cat_terms, continuous_vars, mapping_obs)
2328
# is the formula term even active?
2429
cat_active = Dict(cat => data[1, cat] != "typical_value" for cat in cat_terms)
2530
cont_active = Dict(cont => data[1, cont] != "typical_value" for cont in continuous_vars)
26-
# @debug "active?" cat_active cont_active
31+
2732
# get the categorical values
2833
cat_levels = [unique(data[!, cat]) for cat in cat_terms]
2934

src/widgets.jl

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ end
5353

5454
"""
5555
mapping_dropdowns(varnames, var_types)
56-
Maps dropdown menus on the left panel of the Figure.\\
56+
Map and arrange dropdown menus on the left panel of the dashboard.\\
5757
5858
Arguments:\\
5959
- `varnames::Vector{Symbol}` - vector of the model formula terms.
@@ -87,7 +87,7 @@ function mapping_dropdowns(varnames, var_types)
8787
:col => $(col_dropdown.value),
8888
:row => $(row_dropdown.value),
8989
)
90-
mapping_dom = Col(
90+
mapping_dom = Col(
9191
Row(DOM.div("color:"), c_dropdown, align_items = "center", justify_items = "end"),
9292
Row(DOM.div("marker:"), m_dropdown, align_items = "center", justify_items = "end"),
9393
Row(
@@ -210,9 +210,27 @@ function rectselect(ax)
210210
return selrect
211211
end
212212

213-
function topoplot_widget(positions; size = ())
213+
"""
214+
topoplot_widget(positions; size = ())
215+
Controls the topoplot in the lower left panel of the figure.\\
216+
Highlight the location of the current electrode and allows electrode selection.
214217
215-
strokecolor = Observable(repeat([:red], length(to_value(positions))))
218+
Arguments:\\
219+
- `positions::Vector{Point{2, Float32}}` - x an y coordinates of the channels.
220+
- `size::Tuple{Float64, Float64}` - size of the topoplot panel.
221+
222+
Actions:
223+
- Create interactive scatter.
224+
- Highlight the selected electrode with white color, others are grayed out.
225+
- Create a topolot with a null interpolator and define its style and behavior.
226+
- Hide decorations and spines.
227+
228+
**Return Values:**
229+
- `h_topo::Makie.FigureAxisPlot` - topoplot widget.
230+
- `interactive_scatter::Observable{Int64}` - number of the selected channel.
231+
"""
232+
function topoplot_widget(positions; size = ())
233+
strokecolor = Observable(repeat([:red], length(to_value(positions)))) # crashing
216234
interactive_scatter = Observable(1)
217235

218236
colorrange = vcat(0, 1)
@@ -239,7 +257,6 @@ function topoplot_widget(positions; size = ())
239257
if isa(plt, Makie.Scatter)
240258
data_obs[] .= 0
241259
data_obs[][p] = 1
242-
@debug data_obs
243260
notify(data_obs)
244261
interactive_scatter[] = p
245262
end
@@ -248,7 +265,6 @@ function topoplot_widget(positions; size = ())
248265
end
249266
hidedecorations!(h_topo.axis)
250267
hidespines!(h_topo.axis)
251-
#xlims!(h_topo.axis, [-0.25 1.25])
252268
return h_topo, interactive_scatter
253269

254270
end

test/basic.jl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
dataS, evts, pos2d = gen_data()
2-
formulaS = @formula(0 ~ 1 + luminance + fruit + animal)
3-
times = range(0, length = size(dataS, 2), step = 1 ./ 100)
4-
model = Unfold.fit(UnfoldModel, formulaS, evts, dataS, times)
5-
_, positions = TopoPlots.example_data()
1+
begin
2+
dataS, evts, pos2d = gen_data()
3+
formulaS = @formula(0 ~ 1 + luminance + fruit + animal)
4+
times = range(0, length = size(dataS, 2), step = 1 ./ 100)
5+
model = Unfold.fit(UnfoldModel, formulaS, evts, dataS, times)
6+
_, positions = TopoPlots.example_data()
7+
end
68

79
@testset "basic test" begin
810
explore(model; positions = positions)

0 commit comments

Comments
 (0)