Skip to content

Commit 2cc80f9

Browse files
committed
wip
1 parent b290f88 commit 2cc80f9

File tree

3 files changed

+278
-21
lines changed

3 files changed

+278
-21
lines changed

NetworkDynamicsInspector/src/NetworkDynamicsInspector.jl

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ function gen_state_options(nw::Network, sidxs)
363363
options
364364
end
365365

366-
function timeseries_card(app)
366+
function timeseries_card(app, session)
367367
comp_options = Observable{Vector{OptionGroup{SymbolicIndex}}}()
368368
on(app.sol; update=true) do _sol
369369
@debug "TS: app.sol => comp_options"
@@ -396,22 +396,171 @@ function timeseries_card(app)
396396

397397
# hl choice of elements in graphplot
398398
on(app.tsplot.selcomp; update=true) do _sel
399+
@debug "TS: comp selection => graphplot selection"
399400
if app.graphplot.selcomp[] != _sel
400401
app.graphplot.selcomp[] = _sel
401402
end
402403
nothing
403404
end
404405

406+
####
407+
#### actual plot
408+
####
409+
color_cache = Dict{Union{EIndex{Int,Nothing},VIndex{Int,Nothing}}, Int}()
410+
linestyle_cache = Dict{Symbol,Int}()
411+
on(app.tsplot.selcomp) do _sel
412+
@debug "TS: comp selection => update color_cache"
413+
for unused in setdiff(keys(color_cache), _sel)
414+
delete!(color_cache, unused)
415+
end
416+
for new in setdiff(_sel, keys(color_cache))
417+
i = _smallest_free(color_cache)
418+
color_cache[new] = i
419+
end
420+
nothing
421+
end
422+
on(app.tsplot.states) do _states
423+
@debug "TS: state selection => update linestyle_cache"
424+
for unused in setdiff(keys(linestyle_cache), _states)
425+
delete!(linestyle_cache, unused)
426+
end
427+
for new in setdiff(_states, keys(linestyle_cache))
428+
i = _smallest_free(linestyle_cache)
429+
linestyle_cache[new] = i
430+
end
431+
nothing
432+
end
433+
getcolor = (s) -> begin
434+
key = s isa VIndex ? VIndex(s.compidx) : EIndex(s.compidx)
435+
Cycled(color_cache[key])
436+
Cycled(ckey)
437+
end
438+
getlinestyle = (s) -> Cycled(linestyle_cache[s.subidx])
439+
440+
441+
fig, ax = with_theme(apptheme()) do
442+
fig = Figure()
443+
ax = Axis(fig[1, 1])
444+
fig, ax
445+
end
446+
Main.axref[] = ax
447+
448+
# set axis limits according to time range
449+
onany(app.tmin, app.tmax, update=true) do _tmin, _tmax
450+
@debug "TS: tim/tmax => update ax limits"
451+
xlims!(ax, (_tmin, _tmax))
452+
nothing
453+
end
454+
455+
ts = Observable(range(app.sol[].t[begin], app.sol[].t[end], length=1000))
456+
# last update of ts range
457+
lastupdate = Ref(time())
458+
on(ax.finallimits) do lims
459+
lastupdate[] = time()
460+
nothing
461+
end
462+
# every 0.5 seconds trigger resampling
463+
# timer = Timer(.5; interval=.5) do _
464+
# if time() > lastupdate[] + 0.5
465+
# lims = ax.finallimits[]
466+
# tmin = max(app.sol[].t[begin], lims.origin[1])
467+
# tmax = min(app.sol[].t[end], tmin + lims.widths[1])
468+
# ts[] = range(tmin, tmax, length=1000)
469+
# lastupdate[] = Inf
470+
# end
471+
# end
472+
on(session.on_close) do _
473+
@info "Session closed, time to clean up"
474+
# close(timer)
475+
end
476+
477+
# collect all the states wie might want to plot
478+
valid_idxs = Observable(
479+
Union{VIndex{Int,Symbol},EIndex{Int,Symbol}}[]
480+
)
481+
onany(app.tsplot.selcomp, app.tsplot.states; update=true) do _selcomp, _states
482+
@debug "TS: sel comp/states => update valid_idxs"
483+
isvalid(s) = SII.is_variable(app.sol[], s) || SII.is_parameter(app.sol[], s) || SII.is_observed(app.sol[], s)
484+
empty!(valid_idxs[])
485+
for c in _selcomp, s in _states
486+
idx = c isa VIndex ? VIndex(c.compidx, s) : EIndex(c.compidx, s)
487+
isvalid(idx) && push!(valid_idxs[], idx)
488+
end
489+
notify(valid_idxs)
490+
end
491+
492+
# extract the data
493+
data = Observable{Vector{Vector{Float32}}}(Vector{Float32}[])
494+
onany(ts, valid_idxs, app.tsplot.rel, app.sol; update=true) do _ts, _valid_idxs, _rel, _sol
495+
@debug "TS: t, valid_idx, rel, sol => update data"
496+
_dat = _sol(_ts, idxs=_valid_idxs)
497+
if _rel
498+
u0 = _sol(sol.t[begin], idxs=_valid_idxs)
499+
for row in _dat
500+
row .-= u0
501+
end
502+
end
503+
resize!(data[], length(_valid_idxs))
504+
for i in 1:length(_valid_idxs)
505+
data.val[i] = _dat[i,:]
506+
end
507+
@info "Data updated" data[]
508+
notify(data)
509+
end
510+
511+
# plot the thing
512+
on(data; update=true) do _dat
513+
@debug "TS: Data => Plotting"
514+
println("befor empty")
515+
empty!(ax)
516+
println("after empty")
517+
# vlines!(ax, app.t; color=:black)
518+
# for (idx, y) in zip(valid_idxs[], data[])
519+
for i in 1:length(valid_idxs[])
520+
# @info "plot $idx" y
521+
# lines!(ax, ts[], y; label=string(idx), color=getcolor(idx), linestyle=getlinestyle(idx))
522+
# lines!(ax, ts[], y; label=string(idx))
523+
lines!(ax, rand(3))
524+
end
525+
@debug "End of plotting"
526+
nothing
527+
end
528+
529+
####
530+
#### Click interaction to set time
531+
####
532+
set_time_interaction = (event::MouseEvent, axis) -> begin
533+
if event.type === MouseEventTypes.leftclick
534+
@debug "TS: click on axis => update t"
535+
pos = mouseposition(axis.scene)[1]
536+
app.t[] = pos
537+
return Consume(true)
538+
end
539+
return Consume(false)
540+
end
541+
register_interaction!(set_time_interaction, ax, :set_time)
542+
405543
Card(
406544
Grid(
407545
comp_sel_dom,
408546
state_sel_dom,
547+
fig
409548
)
410549
)
411550
end
412551
function _sidx_to_str(s)
413552
(s isa VIndex ? "v" : "e") * string(s.compidx)
414553
end
554+
function _smallest_free(d::Dict)
555+
vals = values(d)
556+
i = 1
557+
while i vals
558+
i += 1
559+
end
560+
return i
561+
end
562+
563+
415564
function clear_obs!(nt::NamedTuple)
416565
for v in values(nt)
417566
clear_obs!(v)

NetworkDynamicsInspector/src/widgets.jl

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,3 +559,99 @@ function _selection_to_jsselection(options, selection)
559559
end
560560
end
561561
end
562+
563+
struct ToggleSwitch
564+
value::Observable{Bool}
565+
end
566+
567+
function Bonito.jsrender(session::Session, slider::ToggleSwitch)
568+
# main properties
569+
height = 24
570+
width = 35
571+
inset = 3
572+
checked_color = "#2196F3"
573+
background_color = "#ccc"
574+
thumb_color = "white"
575+
transition_time = 0.4
576+
label = "rel to u0"
577+
578+
thumb_diameter = height - 2 * inset
579+
translate = width - thumb_diameter - 2*inset
580+
581+
582+
switch_style = Styles(
583+
"position" => "relative",
584+
"display" => "inline-block",
585+
"width" => "$(width)px",
586+
"height" => "$(height)px",
587+
)
588+
589+
input_style = Styles(
590+
CSS(
591+
"opacity" => "0",
592+
"width" => "0",
593+
"height" => "0",
594+
),
595+
CSS(":checked + .slider",
596+
"background-color" => checked_color,
597+
),
598+
CSS(":focus + .slider",
599+
"box-shadow" => "0 0 1px $(checked_color)",
600+
),
601+
CSS(":checked + .slider:before",
602+
"-webkit-transform" => "translateX($(translate)px)",
603+
"-ms-transform" => "translateX($(translate)px)",
604+
"transform" => "translateX($(translate)px)",
605+
),
606+
)
607+
608+
slider_style = Styles(
609+
CSS(
610+
"position" => "absolute",
611+
"cursor" => "pointer",
612+
"top" => "0",
613+
"left" => "0",
614+
"right" => "0",
615+
# "width" => "$(width)px",
616+
"bottom" => "0",
617+
"background-color" => background_color,
618+
"-webkit-transition" => "$(transition_time)s",
619+
"transition" => "$(transition_time)s",
620+
"border-radius" => "$(height/2)px",
621+
),
622+
CSS(":hover",
623+
# "background-color" => "#2196F3",
624+
),
625+
CSS("::before",
626+
"position" => "absolute",
627+
"content" => "''",
628+
"height" => "$(thumb_diameter)px",
629+
"width" => "$(thumb_diameter)px",
630+
"left" => "$(inset)px",
631+
"bottom" => "$(inset)px",
632+
"background-color" => "white",
633+
"-webkit-transition" => "$(transition_time)s",
634+
"transition" => "$(transition_time)s",
635+
"border-radius" => "50%",
636+
)
637+
)
638+
639+
container = DOM.div(
640+
DOM.label(
641+
DOM.input(
642+
type="checkbox",
643+
checked=slider.value[],
644+
onchange=js"""
645+
(e) => {
646+
$(slider.value).notify(e.target.checked);
647+
}
648+
""";
649+
style=input_style
650+
),
651+
DOM.span(; class="slider", style=slider_style),
652+
DOM.span(label; class="sliederlabel");
653+
class="switch", style=switch_style
654+
)
655+
)
656+
jsrender(session, container)
657+
end

test/Inspector_test.jl

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using NetworkDynamics
2-
using NetworkDynamics: SymbolicIndex
2+
using NetworkDynamics: SymbolicIndex, SII
33
using NetworkDynamicsInspector
44
using Bonito
55
using WGLMakie
@@ -55,7 +55,7 @@ sol = let
5555
sol = solve(prob, Tsit5());
5656
end;
5757

58-
# ENV["JULIA_DEBUG"] = NetworkDynamicsInspector
58+
ENV["JULIA_DEBUG"] = NetworkDynamicsInspector
5959
# ENV["JULIA_DEBUG"] = ""
6060

6161
app = (;
@@ -78,6 +78,7 @@ app = (;
7878
tsplot = (;
7979
selcomp = Observable{Vector{SymbolicIndex}}(SymbolicIndex[]),
8080
states = Observable{Vector{Symbol}}(Symbol[]),
81+
rel = Observable{Bool}(false),
8182
)
8283
);
8384

@@ -95,7 +96,7 @@ let
9596
),
9697
DOM.div(
9798
NetworkDynamicsInspector.timeslider_card(app),
98-
NetworkDynamicsInspector.timeseries_card(app),
99+
NetworkDynamicsInspector.timeseries_card(app, session),
99100
class="timeseries-col"
100101
),
101102
class="maingrid"
@@ -104,23 +105,34 @@ let
104105
serve_app(_app)
105106
end
106107

108+
server_ref = Ref(nothing)
109+
let
110+
_app = App() do session
111+
fig = Figure()
112+
ax = Axis(fig[1,1])
113+
button = Bonito.Button("add plot")
107114

108-
app.tsplot.states[] = [ , :P]
109-
notify(app.tsplot.states)
110-
app.tsplot.selcomp[]
111-
112-
sol([0, 2], idxs=EIndex.(2:5,:active))
113-
114-
sol = app.sol[]
115-
nw = NetworkDynamics.extract_nw(sol)
116-
sidxs = app.tsplot.selcomp[]
117-
NetworkDynamicsInspector.gen_state_options(nw, sidxs)
118-
119-
120-
idxs = EIndex.(1:7, :active);
121-
rel = false;
122-
NetworkDynamicsInspector._maxrange(sol, idxs, rel)
115+
on(button.value) do _
116+
scatter!(ax, rand(3))
117+
end
123118

124-
using WGLMakie.Colors
119+
Grid(Card(fig), button)
120+
end;
121+
if !isnothing(server_ref[]) && Bonito.HTTPServer.isrunning(server_ref[])
122+
@info "Stop running server..."
123+
close(SERVER[])
124+
end
125+
server_ref[] = Bonito.Server(_app, "0.0.0.0", 8080)
126+
end
125127

126-
ColorScheme((colorant"gray",colorant"gray"))
128+
tog = Observable{Bool}(false)
129+
on(tog) do state
130+
@info "value = $state"
131+
end
132+
let
133+
_app = App() do session
134+
toggle = NetworkDynamicsInspector.ToggleSwitch(tog)
135+
toggle
136+
end;
137+
serve_app(_app)
138+
end

0 commit comments

Comments
 (0)