@@ -12,6 +12,8 @@ using NetworkDynamics: SII
12
12
using Graphs: nv, ne
13
13
using GraphMakie
14
14
using GraphMakie. NetworkLayout
15
+ using Bonito. Hyperscript
16
+ using Bonito. Tables. OrderedCollections
15
17
16
18
const JQUERY
= Asset (
" https://cdn.jsdelivr.net/npm/[email protected] /dist/jquery.min.js" )
17
19
const SELECT2_CSS
= Asset (
" https://cdn.jsdelivr.net/npm/[email protected] /dist/css/select2.min.css" )
@@ -36,7 +38,7 @@ function apptheme()
36
38
)
37
39
end
38
40
39
- function graphplot_card (app; kwargs ... )
41
+ function graphplot_card (app, session )
40
42
nw = map! (extract_nw, Observable {Network} (), app. sol)
41
43
NV = nv (nw[])
42
44
NE = ne (nw[])
@@ -110,11 +112,11 @@ function graphplot_card(app; kwargs...)
110
112
SMALL = 30
111
113
BIG = 50
112
114
node_size = Observable (fill (SMALL, NV))
113
- THIN = 3
114
- THICK = 6
115
+ THIN = 5
116
+ THICK = 8
115
117
edge_width = Observable (fill (THIN, NE))
116
118
117
- onany (app. graphplot. selcomp ; update= true ) do selcomp
119
+ onany (app. graphplot. _selcomp ; update= true ) do selcomp
118
120
@debug " GP: Sel comp => node_size, edge_width"
119
121
fill! (node_size[], SMALL)
120
122
fill! (edge_width[], THIN)
@@ -144,6 +146,7 @@ function graphplot_card(app; kwargs...)
144
146
Stress (; pin)
145
147
end
146
148
149
+ small_hover_text = Observable {String} (" " )
147
150
fig, ax = with_theme (apptheme ()) do
148
151
fig = Figure (; figure_padding= 0 )
149
152
ax = Axis (fig[1 ,1 ])
@@ -153,6 +156,11 @@ function graphplot_card(app; kwargs...)
153
156
154
157
hidespines! (ax)
155
158
hidedecorations! (ax)
159
+
160
+ fig[1 ,1 ] = Label (fig, small_hover_text,
161
+ tellwidth= false , tellheight= false ,
162
+ justification= :left , halign= :left , valign= :bottom )
163
+
156
164
fig, ax
157
165
end
158
166
xratio = Ref {Float64} (1.0 )
@@ -162,7 +170,59 @@ function graphplot_card(app; kwargs...)
162
170
adapt_xy_scaling! (xratio, yratio, ax)
163
171
nothing
164
172
end
165
- Card (fig; class= " graphplot-card" , kwargs... )
173
+
174
+ # ###
175
+ # ### Interactions
176
+ # ###
177
+ hoverstate = Observable (false )
178
+
179
+ js = js """
180
+ const gpcard = document .querySelector (" .graphplot-card" );
181
+ $ (hoverstate).on ((state ) => {
182
+ if (state) {
183
+ gpcard .style .cursor = " pointer" ;
184
+ } else {
185
+ gpcard .style .cursor = " default" ;
186
+ }
187
+ });
188
+ """
189
+ evaljs (session, js)
190
+
191
+ nhh = NodeHoverHandler () do hstate, idx, event, axis
192
+ hoverstate[] = hstate
193
+ app. graphplot. _hoverel[] = hstate ? VIndex (idx) : nothing
194
+ end
195
+ ehh = EdgeHoverHandler () do hstate, idx, event, axis
196
+ hoverstate[] = hstate
197
+ app. graphplot. _hoverel[] = hstate ? EIndex (idx) : nothing
198
+ end
199
+
200
+ on (app. graphplot. _hoverel) do el
201
+ small_hover_text[] = isnothing (el) ? " " : repr (el)
202
+ end
203
+
204
+ # interactions
205
+ clickaction = (i, type) -> begin
206
+ idx = type == :vertex ? VIndex (i) : EIndex (i)
207
+ selcomp = app. tsplots[][app. active_tsplot[]]. selcomp
208
+ if idx ∉ selcomp[]
209
+ push! (selcomp[], idx)
210
+ else
211
+ filter! (x -> x != idx, selcomp[])
212
+ end
213
+ app. graphplot. _lastclickel[] = idx
214
+ notify (selcomp)
215
+ end
216
+ nch = NodeClickHandler ((i, _, _) -> clickaction (i, :vertex ))
217
+ ech = EdgeClickHandler ((i, _, _) -> clickaction (i, :edge ))
218
+
219
+ register_interaction! (ax, :nodeclick , nch)
220
+ register_interaction! (ax, :nodehover , nhh)
221
+ register_interaction! (ax, :edgeclick , ech)
222
+ register_interaction! (ax, :edgehover , ehh)
223
+
224
+
225
+ Card (fig; class= " graphplot-card" )
166
226
end
167
227
function _gracefully_extract_states! (vec, sol, t, idxs, rel)
168
228
isvalid (s) = SII. is_variable (sol, s) || SII. is_parameter (sol, s) || SII. is_observed (sol, s)
@@ -358,7 +418,42 @@ function _maxrange(sol, idxs, rel)
358
418
extrema (Iterators. flatten (u_for_t))
359
419
end
360
420
361
- function timeseries_card (app, session)
421
+ function timeseries_cards (app, session)
422
+ cards = OrderedDict {String,Hyperscript.Node{Hyperscript.HTMLSVG}} ()
423
+ container = Observable {Hyperscript.Node{Hyperscript.HTMLSVG}} ()
424
+
425
+ on (app. tsplots; update= true ) do _tsplots
426
+ @debug " TS: app.tsplots => update timeseries cards"
427
+ newkeys = keys (_tsplots)
428
+ knownkeys = keys (cards)
429
+
430
+ for delkey in setdiff (knownkeys, newkeys)
431
+ delete! (cards, delkey)
432
+ end
433
+ for newkey in setdiff (newkeys, knownkeys)
434
+ cards[newkey] = timeseries_card (app, newkey, session)
435
+ # cards[newkey] = DOM.div(scatter(rand(100)))
436
+ end
437
+ if keys (cards) != keys (_tsplots)
438
+ @warn " The keys do not match: $(keys (cards)) vs $(keys (_tsplots)) "
439
+ end
440
+
441
+ container[] = DOM. div (values (cards)... ; class= " timeseries-stack" )
442
+
443
+ nothing
444
+ end
445
+
446
+ on (app. active_tsplot; update= true ) do active
447
+ activesel = app. tsplots[][active]. selcomp[]
448
+ app. graphplot. _selcomp[] = activesel
449
+ end
450
+
451
+ return container[]
452
+ end
453
+
454
+ function timeseries_card (app, key, session)
455
+ tsplot = app. tsplots[][key]
456
+
362
457
comp_options = Observable {Vector{OptionGroup{SymbolicIndex}}} ()
363
458
on (app. sol; update= true ) do _sol
364
459
@debug " TS: app.sol => comp_options"
@@ -370,21 +465,21 @@ function timeseries_card(app, session)
370
465
end
371
466
372
467
state_options = Observable {Vector{OptionGroup{Symbol}}} ()
373
- onany (app. sol, app . tsplot. selcomp; update= true ) do _sol, _sel
374
- @debug " TS: app.sol, app. tsplot.selcomp => state_options"
468
+ onany (app. sol, tsplot. selcomp; update= true ) do _sol, _sel
469
+ @debug " TS: app.sol, tsplot.selcomp => state_options"
375
470
_nw = extract_nw (_sol)
376
471
state_options[] = gen_state_options (_nw, _sel)
377
472
nothing
378
473
end
379
474
380
- comp_sel = MultiSelect (comp_options, app . tsplot. selcomp;
475
+ comp_sel = MultiSelect (comp_options, tsplot. selcomp;
381
476
placeholder= " Select components" ,
382
477
multi= true ,
383
478
option_to_string= _sidx_to_str,
384
479
T= SymbolicIndex,
385
480
id= gendomid (" compsel" ))
386
481
# comp_sel_dom = Grid(DOM.span("Components"), comp_sel; columns = "70px 1fr", align_items = "center")
387
- state_sel = MultiSelect (state_options, app . tsplot. states;
482
+ state_sel = MultiSelect (state_options, tsplot. states;
388
483
placeholder= " Select states" ,
389
484
multi= true ,
390
485
T= Symbol,
@@ -394,11 +489,11 @@ function timeseries_card(app, session)
394
489
on (reset_button. value) do _
395
490
empty! (color_cache)
396
491
empty! (linestyle_cache)
397
- notify (app . tsplot. selcomp)
398
- notify (app . tsplot. states)
492
+ notify (tsplot. selcomp)
493
+ notify (tsplot. states)
399
494
end
400
495
401
- rel_toggle = ToggleSwitch (value= app . tsplot. rel, label= " Rel to u0" )
496
+ rel_toggle = ToggleSwitch (value= tsplot. rel, label= " Rel to u0" )
402
497
403
498
comp_state_sel_dom = Grid (
404
499
DOM. span (" Components" ), comp_sel, reset_button,
@@ -409,11 +504,9 @@ function timeseries_card(app, session)
409
504
)
410
505
411
506
# hl choice of elements in graphplot
412
- on (app . tsplot. selcomp; update= true ) do _sel
507
+ on (tsplot. selcomp; update= true ) do _sel
413
508
@debug " TS: comp selection => graphplot selection"
414
- if app. graphplot. selcomp[] != _sel
415
- app. graphplot. selcomp[] = _sel
416
- end
509
+ app. graphplot. _selcomp[] = _sel
417
510
nothing
418
511
end
419
512
@@ -429,7 +522,7 @@ function timeseries_card(app, session)
429
522
colorpairs = Observable {Vector{@NamedTuple{title::String,color::String}}} ()
430
523
lstylepairs = Observable {Vector{@NamedTuple{title::String,linestyle::String}}} ()
431
524
432
- on (app . tsplot. selcomp; update= true ) do _sel
525
+ on (tsplot. selcomp; update= true ) do _sel
433
526
@debug " TS: comp selection => update color_cache"
434
527
for unused in setdiff (keys (color_cache), _sel)
435
528
delete! (color_cache, unused)
@@ -443,7 +536,7 @@ function timeseries_card(app, session)
443
536
for (k,v) in color_cache]
444
537
nothing
445
538
end
446
- on (app . tsplot. states; update= true ) do _states
539
+ on (tsplot. states; update= true ) do _states
447
540
@debug " TS: state selection => update linestyle_cache"
448
541
for unused in setdiff (keys (linestyle_cache), _states)
449
542
delete! (linestyle_cache, unused)
@@ -567,16 +660,16 @@ function timeseries_card(app, session)
567
660
# lastupdate[] = Inf
568
661
# end
569
662
# end
570
- on (session. on_close) do _
571
- @info " Session closed, time to clean up"
572
- # close(timer)
573
- end
663
+ # on(session.on_close) do _
664
+ # @info "Session closed, time to clean up"
665
+ # # close(timer)
666
+ # end
574
667
575
668
# collect all the states wie might want to plot
576
669
valid_idxs = Observable (
577
670
Union{VIndex{Int,Symbol},EIndex{Int,Symbol}}[]
578
671
)
579
- onany (app . tsplot. selcomp, app . tsplot. states; update= true ) do _selcomp, _states
672
+ onany (tsplot. selcomp, tsplot. states; update= true ) do _selcomp, _states
580
673
@debug " TS: sel comp/states => update valid_idxs"
581
674
isvalid (s) = SII. is_variable (app. sol[], s) || SII. is_parameter (app. sol[], s) || SII. is_observed (app. sol[], s)
582
675
empty! (valid_idxs[])
@@ -589,7 +682,7 @@ function timeseries_card(app, session)
589
682
590
683
# extract the data
591
684
data = Observable {Vector{Vector{Float32}}} (Vector{Float32}[])
592
- onany (ts, valid_idxs, app . tsplot. rel, app. sol; update= true ) do _ts, _valid_idxs, _rel, _sol
685
+ onany (ts, valid_idxs, tsplot. rel, app. sol; update= true ) do _ts, _valid_idxs, _rel, _sol
593
686
@debug " TS: t, valid_idx, rel, sol => update data"
594
687
_dat = _sol (_ts, idxs= _valid_idxs)
595
688
if _rel
@@ -640,14 +733,38 @@ function timeseries_card(app, session)
640
733
end
641
734
register_interaction! (set_time_interaction, ax, :set_time )
642
735
643
- Card (
736
+ cardclass = " timeseries-card"
737
+ if key == app. active_tsplot[]
738
+ cardclass *= " active-tseries"
739
+ end
740
+
741
+ card = Card (
644
742
DOM. div (
645
743
comp_state_sel_dom,
646
744
DOM. div (fig; class= " timeseries-axis-container" );
647
745
class= " timeseries-card-container"
648
746
);
649
- class= " timeseries-card "
747
+ class= cardclass
650
748
)
749
+
750
+ # on click set active-tseries class
751
+ click = js """
752
+ (card ) => {
753
+ card .addEventListener (" click" , function (event ) {
754
+ $ (app .active_tsplot ).notify ($ (key));
755
+
756
+ document .querySelectorAll (" .timeseries-card" ).forEach (element => {
757
+ element .classList .remove (" active-tseries" );
758
+ });
759
+
760
+ // Add "active-tseries" to the given target element
761
+ card .classList .add (" active-tseries" );
762
+ }, { capture: true });
763
+ }
764
+ """
765
+ Bonito. onload (session, card, click)
766
+
767
+ return card
651
768
end
652
769
function _sidx_to_str (s)
653
770
(s isa VIndex ? " v" : " e" ) * string (s. compidx)
0 commit comments