Skip to content

Commit 97c1a13

Browse files
committed
store appstate in special structs
1 parent ffbf132 commit 97c1a13

File tree

4 files changed

+145
-58
lines changed

4 files changed

+145
-58
lines changed

NetworkDynamicsInspector/src/NetworkDynamicsInspector.jl

Lines changed: 138 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module NetworkDynamicsInspector
22

3-
using Bonito: Bonito, @js_str, App, Asset, CSS, Styles,
3+
using Bonito: Bonito, @js_str, Asset, CSS, Styles,
44
Grid, Card, DOM, Session
55
using NetworkDynamics: NetworkDynamics, SII, EIndex, VIndex, Network,
66
get_metadata, has_metadata, get_position, has_position,
@@ -45,47 +45,115 @@ const SymbolicCompIndex = Union{VIndex{Int,Nothing}, EIndex{Int,Nothing}}
4545

4646
export inspect
4747

48-
function wrapsol(sol)
49-
@assert extract_nw(sol) isa Network "sol must be a NetworkDynamics solution"
50-
(;
51-
sol = Observable{Any}(sol),
52-
t = Observable{Float64}(sol.t[begin]),
53-
tmin = Observable{Float64}(sol.t[begin]),
54-
tmax = Observable{Float64}(sol.t[end]),
55-
active_tsplot = Observable{String}("ts-1"),
56-
graphplot = (;
57-
nstate = Observable{Vector{Symbol}}([]),
58-
estate = Observable{Vector{Symbol}}([]),
59-
nstate_rel = Observable{Bool}(false),
60-
estate_rel = Observable{Bool}(false),
61-
ncolorrange = Observable{Tuple{Float32,Float32}}((-1.0, 1.0)),
62-
ncolorscheme = Observable{ColorScheme}(ColorSchemes.coolwarm),
63-
ecolorrange = Observable{Tuple{Float32,Float32}}((-1.0, 1.0)),
64-
ecolorscheme = Observable{ColorScheme}(ColorSchemes.coolwarm),
65-
_selcomp = Observable{Vector{SymbolicCompIndex}}(SymbolicCompIndex[]),
66-
_hoverel = Observable{Union{SymbolicCompIndex,Nothing}}(nothing),
67-
_lastclickel = Observable{Union{SymbolicCompIndex,Nothing}}(nothing),
68-
),
69-
tsplots = Observable{Any}(OrderedDict(
70-
"ts-1" => (;
71-
selcomp = Observable{Vector{SymbolicCompIndex}}(SymbolicCompIndex[]),
72-
states = Observable{Vector{Symbol}}(Symbol[]),
73-
rel = Observable{Bool}(false),
74-
),
75-
"ts-2" => (;
76-
selcomp = Observable{Vector{SymbolicCompIndex}}(SymbolicCompIndex[]),
77-
states = Observable{Vector{Symbol}}(Symbol[]),
78-
rel = Observable{Bool}(false),
79-
),
80-
))
81-
);
48+
@kwdef struct GraphPlot
49+
nstate::Observable{Vector{Symbol}} = [:nothing]
50+
estate::Observable{Vector{Symbol}} = [:nothing]
51+
nstate_rel::Observable{Bool} = false
52+
estate_rel::Observable{Bool} = false
53+
ncolorrange::Observable{Tuple{Float32,Float32}} = (-1.0, 1.0)
54+
ncolorscheme::Observable{ColorScheme} = ColorSchemes.coolwarm
55+
ecolorrange::Observable{Tuple{Float32,Float32}} = (-1.0, 1.0)
56+
ecolorscheme::Observable{ColorScheme} = ColorSchemes.coolwarm
57+
_selcomp::Observable{Vector{SymbolicCompIndex}} = SymbolicCompIndex[]
58+
_hoverel::Observable{Union{SymbolicCompIndex,Nothing}} = nothing
59+
_lastclickel::Observable{Union{SymbolicCompIndex,Nothing}} = nothing
60+
end
61+
function GraphPlot(sol)
62+
nw = extract_nw(sol)
63+
estate = [_most_common_output_state(nw.im.edgem)]
64+
nstate = [_most_common_output_state(nw.im.vertexm)]
65+
GraphPlot(; nstate, estate)
66+
end
67+
function _most_common_output_state(models)
68+
states = mapreduce(NetworkDynamics.outsym_flat, vcat, models)
69+
unique_states = unique(states)
70+
counts = map(unique_states) do s
71+
count(isequal(s), states)
72+
end
73+
unique_states[argmax(counts)]
74+
end
75+
76+
@kwdef struct TimeseriesPlot
77+
selcomp::Observable{Vector{SymbolicCompIndex}} = SymbolicCompIndex[]
78+
states::Observable{Vector{Symbol}} = Symbol[]
79+
rel::Observable{Bool} = false
80+
end
81+
82+
struct AppState
83+
sol::Observable{Any}
84+
t::Observable{Float64}
85+
tmin::Observable{Float64}
86+
tmax::Observable{Float64}
87+
active_tsplot::Observable{String}
88+
graphplot::GraphPlot
89+
tsplots::Observable{OrderedDict{String, TimeseriesPlot}}
90+
end
91+
function AppState(sol::SciMLBase.AbstractODESolution)
92+
t = sol.t[begin]
93+
tmin = sol.t[begin]
94+
tmax = sol.t[end]
95+
graphplot = GraphPlot(sol)
96+
tsplots = OrderedDict(
97+
"ts-1" => TimeseriesPlot(),
98+
"ts-2" => TimeseriesPlot()
99+
)
100+
active_tsplot = "ts-1"
101+
AppState(sol, t, tmin, tmax, active_tsplot, graphplot, tsplots)
102+
end
103+
104+
const APPSTATE = Ref{Union{Nothing,AppState}}(nothing)
105+
const SERVER = Ref{Any}(nothing)
106+
const SESSION = Ref{Union{Nothing,Session}}(nothing)
107+
108+
function reset!(sol=nothing)
109+
if isnothing(APPSTATE[]) && isnothing(sol)
110+
error("No appstate to reset")
111+
else
112+
clear_obs!(APPSTATE[])
113+
if isnothing(sol)
114+
APPSTATE[] = AppState(APPSTATE[].sol[])
115+
else
116+
APPSTATE[] = AppState(sol)
117+
end
118+
end
119+
end
120+
121+
server_running() = !isnothing(SERVER[]) && Bonito.HTTPServer.isrunning(SERVER[])
122+
123+
function stop_server!()
124+
if !isnothing(SESSION[])
125+
if Base.isopen(SESSION[])
126+
@info "Close running session..."
127+
close(SESSION[])
128+
end
129+
SESSION[] = nothing
130+
end
131+
if server_running()
132+
@info "Stop running server..."
133+
close(SERVER[])
134+
end
82135
end
83136

84-
inspect(sol::SciMLBase.AbstractODESolution) = inspect(wrapsol(sol))
137+
function start_server!(restart=true)
138+
if server_running() && !restart
139+
error("Server already running")
140+
end
141+
stop_server!()
142+
143+
if isnothing(APPSTATE[])
144+
error("No appstate to restart")
145+
end
146+
147+
app = APPSTATE[]
148+
149+
webapp = Bonito.App() do session
150+
@info "New GUI Session started"
151+
if !isnothing(SESSION[]) && Base.isopen(SESSION[])
152+
@info "Close previous session..."
153+
close(SESSION[])
154+
end
155+
SESSION[] = session
85156

86-
function inspect(app::NamedTuple)
87-
_app = App() do session
88-
@info "start new session"
89157
WGLMakie.activate!(resize_to=:parent)
90158
clear_obs!(app)
91159

@@ -142,10 +210,39 @@ function inspect(app::NamedTuple)
142210
class="maingrid"
143211
)
144212
end;
145-
serve_app(_app)
146-
return app
213+
214+
SERVER[] = Bonito.Server(webapp, "localhost", 8080)
215+
url = SERVER[].url
216+
port = SERVER[].port
217+
@info "Visit $url:$port to launch App"
147218
end
148219

220+
"""
221+
inspect(sol; restart=false, reset=false)
222+
223+
Main entry point for gui. Starts the server and serves the app for
224+
soution `sol`.
225+
226+
- `restart`: If `true`, stop the server if it is running and start a new one.
227+
- `reset`: If `true`, reset the appstate with the new solution `sol`.
228+
"""
229+
function inspect(sol; restart=false, reset=false)
230+
if restart && server_running()
231+
stop_server!()
232+
end
233+
if isnothing(APPSTATE[]) || reset
234+
reset!(sol)
235+
else
236+
APPSTATE[].sol[] = sol
237+
end
238+
239+
if !server_running()
240+
start_server!()
241+
else
242+
@info "App still served at $(SERVER[].url):$(SERVER[].port)"
243+
end
244+
nothing
245+
end
149246

150247
function apptheme()
151248
Theme(

NetworkDynamicsInspector/src/graphplot.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ function graphplot_card(app, session)
212212
register_interaction!(ax, :edgeclick, ech)
213213
register_interaction!(ax, :edgehover, ehh)
214214

215-
216215
Card(fig; class="graphplot-card")
217216
end
218217
function _gracefully_extract_states!(vec, sol, t, idxs, rel)

NetworkDynamicsInspector/src/timeseries.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@ function timeseries_cards(app, session)
44

55
on(app.tsplots; update=true) do _tsplots
66
@debug "TS: app.tsplots => update timeseries cards"
7-
newkeys = keys(_tsplots)
7+
newkeys = collect(keys(_tsplots)) # collect to preserv order on setdiff
88
knownkeys = keys(cards)
99

1010
for delkey in setdiff(knownkeys, newkeys)
1111
delete!(cards, delkey)
1212
end
1313
for newkey in setdiff(newkeys, knownkeys)
1414
cards[newkey] = timeseries_card(app, newkey, session)
15-
# cards[newkey] = DOM.div(scatter(rand(100)))
1615
end
1716
if keys(cards) != keys(_tsplots)
1817
@warn "The keys do not match: $(keys(cards)) vs $(keys(_tsplots))"

NetworkDynamicsInspector/src/utils.jl

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,15 @@ function clear_obs!(v::AbstractVector)
4141
clear_obs!(el)
4242
end
4343
end
44-
clear_obs!(x) = x
45-
46-
47-
SERVER = Ref{Any}(nothing)
48-
export serve_app
49-
function serve_app(newapp)
50-
if !isnothing(SERVER[]) && Bonito.HTTPServer.isrunning(SERVER[])
51-
@info "Stop running server..."
52-
close(SERVER[])
44+
function clear_obs!(x::T) where {T}
45+
if T <: Union{GraphPlot, TimeseriesPlot, AppState}
46+
for f in fieldnames(T)
47+
clear_obs!(getfield(x, f))
48+
end
5349
end
54-
SERVER[] = Bonito.Server(newapp, "0.0.0.0", 8080)
55-
url = SERVER[].url
56-
port = SERVER[].port
57-
@info "Visit $url:$port to launch App"
50+
x
5851
end
5952

60-
6153
NetworkDynamics.extract_nw(o::Observable) = extract_nw(o.val)
6254
function NetworkDynamics.extract_nw(o::NamedTuple)
6355
if haskey(o, :sol)

0 commit comments

Comments
 (0)