Skip to content

Commit b290f88

Browse files
committed
start work on ts card
1 parent d9adca5 commit b290f88

File tree

4 files changed

+121
-49
lines changed

4 files changed

+121
-49
lines changed

NetworkDynamicsInspector/src/NetworkDynamicsInspector.jl

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ using Bonito: Grid, @js_str, onload, jsrender
77
using WGLMakie.Makie
88
using WGLMakie.Makie.Colors
99
using WGLMakie.Makie.ColorSchemes
10-
using NetworkDynamics: extract_nw
10+
using NetworkDynamics: extract_nw, SymbolicIndex
1111
using NetworkDynamics: SII
1212
using Graphs: nv, ne
1313
using GraphMakie
@@ -69,6 +69,7 @@ function graphplot_card(app; kwargs...)
6969
error("Received more than one node state to plot...")
7070
end
7171
notify(node_state)
72+
nothing
7273
end;
7374

7475
edge_state = Observable(Vector{Float32}(undef, NE))
@@ -83,6 +84,7 @@ function graphplot_card(app; kwargs...)
8384
error("Received more than one edge state to plot...")
8485
end
8586
notify(edge_state)
87+
nothing
8688
end;
8789

8890
node_color = Observable(Vector{RGB{Float64}}(undef, NV))
@@ -92,6 +94,7 @@ function graphplot_card(app; kwargs...)
9294
node_color[][i] = isnan(statevec[i]) ? RGB(0,0,0) : get(scheme, statevec[i], range)
9395
end
9496
notify(node_color)
97+
nothing
9598
end
9699

97100
edge_color = Observable(Vector{RGB{Float64}}(undef, NE))
@@ -101,32 +104,32 @@ function graphplot_card(app; kwargs...)
101104
edge_color[][i] = isnan(statevec[i]) ? RGB(0,0,0) : get(scheme, statevec[i], range)
102105
end
103106
notify(edge_color)
107+
nothing
104108
end
105109

106110
notify(app.t) # trigger updates
107111

108112
SMALL = 30
109113
BIG = 50
110114
node_size = Observable(fill(SMALL, NV))
111-
onany(app.sel_nodes; update=true) do selected
112-
@debug "GP: app.sel_nodes => node_size"
113-
fill!(node_size[], SMALL)
114-
for sel in selected
115-
node_size[][sel] = BIG
116-
end
117-
notify(node_size)
118-
end
119-
120115
THIN = 3
121116
THICK = 6
122117
edge_width = Observable(fill(THIN, NE))
123-
onany(app.sel_edges; update=true) do selected
124-
@debug "GP: app.sel_edges => edge_width"
118+
119+
onany(app.graphplot.selcomp) do selcomp
120+
@debug "GP: Sel comp => node_size, edge_width"
121+
fill!(node_size[], SMALL)
125122
fill!(edge_width[], THIN)
126-
for sel in selected
127-
edge_width[][sel] = THICK
123+
for s in selcomp
124+
if s isa VIndex
125+
node_size[][s.compidx] = BIG
126+
else
127+
edge_width[][s.compidx] = THICK
128+
end
128129
end
130+
notify(node_size)
129131
notify(edge_width)
132+
nothing
130133
end
131134

132135
g = @lift $(nw).im.g
@@ -157,6 +160,7 @@ function graphplot_card(app; kwargs...)
157160
on(ax.scene.viewport) do lims
158161
@debug "GP: viewport => adapt xy scaling"
159162
adapt_xy_scaling!(ax)
163+
nothing
160164
end
161165
Card(fig; class="graphplot-card", kwargs...)
162166
end
@@ -201,6 +205,7 @@ function timeslider_card(app)
201205
@debug "app.tmin, app.tmax => clamp app.t[]"
202206
app.t[] = _t
203207
end
208+
nothing
204209
end
205210
t_slider = ContinuousSlider(twindow, app.t; arrowkeys=true)
206211
Card(
@@ -233,7 +238,8 @@ function gpstate_control_card(app, type)
233238
on(app.sol; update=true) do _sol
234239
_nw = extract_nw(_sol)
235240
idxs = VEIndex.(1:nv(_nw))
236-
options[] = state_options(_nw, idxs)
241+
options[] = gen_state_options(_nw, idxs)
242+
nothing
237243
end
238244
multisel = MultiSelect(options, stateobs; placeholder="Select state for coloring", multi=false, T=Symbol)
239245
selector = Grid(
@@ -281,13 +287,15 @@ function gpstate_control_card(app, type)
281287
else
282288
error("More than one state for maxrange calculation...")
283289
end
290+
nothing
284291
end;
285292

286293
onany(thumb_l, thumb_r; update=true) do _thumb_l, _thumb_r
287294
@debug "GP: $(type) color slider => app.gp.colorrange"
288295
# store the thumb position
289296
thumb_pos_cache[thumb_pos_key()] = (_thumb_l, _thumb_r)
290297
colorrange[] = (_thumb_l, _thumb_r)
298+
nothing
291299
end
292300

293301
fig = with_theme(apptheme()) do
@@ -326,14 +334,15 @@ function _maxrange(sol, idxs, rel)
326334
extrema(Iterators.flatten(u_for_t))
327335
end
328336

329-
function state_options(nw::Network, sidxs)
337+
function gen_state_options(nw::Network, sidxs)
338+
options = OptionGroup{Symbol}[]
339+
isempty(sidxs) && return options
330340
groups = [
331341
("Outputs & States", cf -> unique!(vcat(NetworkDynamics.outsym_flat(cf), sym(cf)))),
332-
("Inputs", NetworkDynamics.insym_all),
342+
("Inputs", cf -> collect(NetworkDynamics.insym_all(cf))),
333343
("Observables", obssym),
334344
("Parameters", psym),
335345
]
336-
options = OptionGroup{Symbol}[]
337346
exclusive_syms = Symbol[]
338347
for (label, getter) in groups
339348
common_syms = mapreduce(, sidxs) do sidx
@@ -354,6 +363,55 @@ function state_options(nw::Network, sidxs)
354363
options
355364
end
356365

366+
function timeseries_card(app)
367+
comp_options = Observable{Vector{OptionGroup{SymbolicIndex}}}()
368+
on(app.sol; update=true) do _sol
369+
@debug "TS: app.sol => comp_options"
370+
g = extract_nw(_sol).im.g
371+
vg = OptionGroup{SymbolicIndex}("Nodes", VIndex.(1:nv(g)))
372+
eg = OptionGroup{SymbolicIndex}("Edges", EIndex.(1:ne(g)))
373+
comp_options[] = [vg, eg]
374+
nothing
375+
end
376+
377+
state_options = Observable{Vector{OptionGroup{Symbol}}}()
378+
onany(app.sol, app.tsplot.selcomp; update=true) do _sol, _sel
379+
@debug "TS: app.sol, app.tsplot.selcomp => state_options"
380+
_nw = extract_nw(_sol)
381+
state_options[] = gen_state_options(_nw, _sel)
382+
nothing
383+
end
384+
385+
comp_sel = MultiSelect(comp_options, app.tsplot.selcomp;
386+
placeholder="Select components",
387+
multi=true,
388+
option_to_string=_sidx_to_str,
389+
T=SymbolicIndex)
390+
comp_sel_dom = Grid(DOM.span("States"), comp_sel; columns = "70px 1fr", align_items = "center")
391+
state_sel = MultiSelect(state_options, app.tsplot.states;
392+
placeholder="Select states",
393+
multi=true,
394+
T=Symbol)
395+
state_sel_dom = Grid(DOM.span("Components"), state_sel; columns = "70px 1fr", align_items = "center")
396+
397+
# hl choice of elements in graphplot
398+
on(app.tsplot.selcomp; update=true) do _sel
399+
if app.graphplot.selcomp[] != _sel
400+
app.graphplot.selcomp[] = _sel
401+
end
402+
nothing
403+
end
404+
405+
Card(
406+
Grid(
407+
comp_sel_dom,
408+
state_sel_dom,
409+
)
410+
)
411+
end
412+
function _sidx_to_str(s)
413+
(s isa VIndex ? "v" : "e") * string(s.compidx)
414+
end
357415
function clear_obs!(nt::NamedTuple)
358416
for v in values(nt)
359417
clear_obs!(v)

NetworkDynamicsInspector/src/widgets.jl

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ function ContinuousSlider(range, value_l::Observable{T}, value_r::Observable{T};
8888
value_r[] = r_c
8989
end
9090
changed && @debug "Slider: range changed => clamped thumb values"
91+
nothing
9192
end
9293
=#
9394

@@ -344,23 +345,23 @@ function Bonito.jsrender(session::Session, multiselect::MultiSelect)
344345

345346
# generate internal observables of js representation of options and selection
346347
jsoptions = @lift options_to_jsoptions($(multiselect.options); option_to_string=multiselect.option_to_string)
347-
jsselection = Observable{Vector{Int}}(
348-
selection_to_jsselection(multiselect.options[], multiselect.selection[]))
348+
jsselection = Observable{Vector{Int}}()
349349

350350
onany(jsselection) do _jssel
351351
sel = jsselection_to_selection(multiselect.options[], _jssel)
352352
# @info "New jsselection triggers new selection:" _jssel sel
353353
if sel != multiselect.selection[]
354354
multiselect.selection[] = sel
355355
end
356+
nothing
356357
end
357358

358-
onany(multiselect.options, multiselect.selection) do _opts, _sel
359+
onany(multiselect.options, multiselect.selection; update=true) do _opts, _sel
359360
if !multiselect.multi && length(_sel) > 1
360361
deleteat!(_sel, firstindex(_sel)+1:lastindex(_sel))
361362
end
362363
jssel = selection_to_jsselection(_opts, _sel)
363-
# @info "New options or selection triggers new jsselection:" _opts _sel jssel
364+
@debug "MS \"$(multiselect.placeholder)\": New opts or sel triggers new jsselection:" _opts _sel jssel
364365

365366
# filter out invalid selections
366367
if any(isnothing, jssel)
@@ -369,9 +370,8 @@ function Bonito.jsrender(session::Session, multiselect::MultiSelect)
369370
filter!(!isnothing, jssel)
370371
end
371372

372-
if jssel != jsselection[]
373-
jsselection[] = jssel
374-
end
373+
jsselection[] = jssel
374+
nothing
375375
end
376376

377377
# Create a multi-select element
@@ -446,12 +446,10 @@ function Bonito.jsrender(session::Session, multiselect::MultiSelect)
446446
function updateDisplayedSelection(new_sel_nr) {
447447
const jq_select = $jqselect
448448
const new_sel = new_sel_nr.map(String);
449-
const old_sel = jq_select.data('preserved-order') || [];
450-
if (!array_equal(new_sel, old_sel)){
451-
jq_select.data('preserved-order', new_sel);
452-
jq_select.val(new_sel).trigger('change');
453-
select2_renderSelections();
454-
}
449+
450+
jq_select.data('preserved-order', new_sel);
451+
jq_select.val(new_sel).trigger('change');
452+
select2_renderSelections();
455453
}
456454
updateDisplayedSelection($(jsselection).value)
457455
$(jsselection).on(updateDisplayedSelection)

NetworkDynamicsInspector/test/multiselect_test.jl

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,14 @@ let
1717
app = App(;) do session
1818
NetworkDynamicsInspector.clear_obs!(gui)
1919

20-
ms1 = MultiSelect(gui.options, gui.selection; placeholder="pick language", T=Symbol)
21-
ms2 = MultiSelect(gui.options, gui.selection_single; placeholder="pick language", multi=false, T=Symbol)
20+
ms1 = MultiSelect(gui.options, gui.selection; placeholder="multi", T=Symbol)
21+
ms2 = MultiSelect(gui.options, gui.selection_single; placeholder="single", multi=false, T=Symbol)
2222

2323
return wrap_assets(Grid(ms1, ms2; columns="100%", width="500px"))
2424
end;
2525
serve_app(app)
2626
end
2727

28-
foo = Ref{Any}()
29-
isdefined(foo,1)
30-
foo[] = 2
31-
32-
33-
(SERVER[])
34-
s = SERVER[]
35-
3628
@test gui.selection[] == [:Rust, :French]
3729
gui.selection[] = [:Rust, :French, :Julia, :foo]
3830
gui.selection[] = []
@@ -43,11 +35,21 @@ gui.selection_single[] = [:French, :Julia]
4335
@test gui.selection_single[] == [:French]
4436
gui.selection_single[] = []
4537

46-
gui.selection[] = [:Rust, :French]
38+
gui.selection[] = [:German, :Rust, :French]
4739
gui.selection_single[] = [:French]
4840
# change options
4941
gui.options[] = [
50-
OptionGroup("Programming Languages", [:Julia, :Rust, :Java]),
42+
OptionGroup("Languages", [:French, :Spanish, :German]),
43+
]
44+
@test gui.selection[] == [:German, :French]
45+
@test gui.selection_single[] == [:French]
46+
47+
48+
# change options but keep number the same
49+
gui.selection[] = [:French]
50+
gui.selection_single[] = [:French]
51+
gui.options[] = [
52+
OptionGroup("Languages", [:French, :Spanish]),
5153
]
52-
@test gui.selection[] == [:Rust]
53-
@test gui.selection_single[] == []
54+
@test gui.selection[] == [:French]
55+
@test gui.selection_single[] == [:French]

test/Inspector_test.jl

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using NetworkDynamics
2+
using NetworkDynamics: SymbolicIndex
23
using NetworkDynamicsInspector
34
using Bonito
45
using WGLMakie
@@ -62,9 +63,9 @@ app = (;
6263
t = Observable{Float64}(0.0),
6364
tmin = Observable{Float64}(sol.t[begin]),
6465
tmax = Observable{Float64}(sol.t[end]),
65-
sel_nodes = Observable{Vector{Int}}(Int[]),
66-
sel_edges = Observable{Vector{Int}}(Int[]),
66+
active_tsplot = Observable{Int}(1),
6767
graphplot = (;
68+
selcomp = Observable{Vector{SymbolicIndex}}(SymbolicIndex[]),
6869
nstate = Observable{Vector{Symbol}}([]),
6970
estate = Observable{Vector{Symbol}}([:P]),
7071
nstate_rel = Observable{Bool}(false),
@@ -73,6 +74,10 @@ app = (;
7374
ncolorscheme = Observable{ColorScheme}(ColorSchemes.coolwarm),
7475
ecolorrange = Observable{Tuple{Float32,Float32}}((-1.0, 1.0)),
7576
ecolorscheme = Observable{ColorScheme}(ColorSchemes.coolwarm),
77+
),
78+
tsplot = (;
79+
selcomp = Observable{Vector{SymbolicIndex}}(SymbolicIndex[]),
80+
states = Observable{Vector{Symbol}}(Symbol[]),
7681
)
7782
);
7883

@@ -90,19 +95,28 @@ let
9095
),
9196
DOM.div(
9297
NetworkDynamicsInspector.timeslider_card(app),
98+
NetworkDynamicsInspector.timeseries_card(app),
9399
class="timeseries-col"
94100
),
95-
# columns="1fr 2fr",
96-
# width="500px",
97101
class="maingrid"
98102
) |> wrap_assets
99103
end;
100104
serve_app(_app)
101105
end
102106

107+
108+
app.tsplot.states[] = [ , :P]
109+
notify(app.tsplot.states)
110+
app.tsplot.selcomp[]
111+
103112
sol([0, 2], idxs=EIndex.(2:5,:active))
104113

105114
sol = app.sol[]
115+
nw = NetworkDynamics.extract_nw(sol)
116+
sidxs = app.tsplot.selcomp[]
117+
NetworkDynamicsInspector.gen_state_options(nw, sidxs)
118+
119+
106120
idxs = EIndex.(1:7, :active);
107121
rel = false;
108122
NetworkDynamicsInspector._maxrange(sol, idxs, rel)

0 commit comments

Comments
 (0)